ThreeModel1.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285
  1. <template>
  2. <div class="three-model">
  3. <loading ref="pageLoading"></loading>
  4. <div class="map-3d" :style="'transform: rotate(' + mapDeg + 'deg);'">
  5. <img :src="require('@assets/png/3dmap.png')" alt="" />
  6. </div>
  7. <div class="three-html-layer">
  8. <div
  9. v-for="(info, index) of htmlLayer"
  10. :key="index"
  11. class="three-html-dom fan-info"
  12. :id="info.id"
  13. v-show="info.show"
  14. :style="'left: ' + info.x + 'px; top: ' + info.y + 'px;'"
  15. >
  16. <div class="fan-name pointer" v-show="info.id != 'build'" @click.stop="info.clickName">
  17. {{ info.name }}
  18. </div>
  19. <div
  20. class="fan-can-click"
  21. @click.stop="info.clickFan"
  22. :style="
  23. 'left: ' +
  24. info.fanX +
  25. 'px; top: ' +
  26. info.fanY +
  27. 'px; width: ' +
  28. info.fanW +
  29. 'px; height: ' +
  30. info.fanH +
  31. 'px;'
  32. "
  33. ></div>
  34. </div>
  35. <div
  36. class="three-html-dom build-info"
  37. :id="fanInfoLayer.id"
  38. v-show="fanInfoLayer.show"
  39. :style="
  40. 'left: ' + fanInfoLayer.x + 'px; top: ' + fanInfoLayer.y + 'px;'
  41. "
  42. >
  43. <div class="build-info-close" @click.stop="hideFanInfo">
  44. <i class="el-icon-close"></i>
  45. </div>
  46. <div
  47. class="build-info-item purple"
  48. :style="
  49. 'left: ' + circleXY[0].x + 'px; top: -' + circleXY[0].y + 'px'
  50. "
  51. >
  52. <div class="build-info-item-num">{{ jkd }}</div>
  53. <div class="build-info-item-text">健康度</div>
  54. </div>
  55. <div
  56. class="build-info-item blue"
  57. :style="
  58. 'left: ' + circleXY[1].x + 'px; top: -' + circleXY[1].y + 'px'
  59. "
  60. >
  61. <div class="build-info-item-num">{{ ysl }}</div>
  62. <div class="build-info-item-text">优</div>
  63. </div>
  64. <div
  65. class="build-info-item yellow"
  66. :style="
  67. 'left: ' + circleXY[2].x + 'px; top: -' + circleXY[2].y + 'px'
  68. "
  69. >
  70. <div class="build-info-item-num">{{ lsl }}</div>
  71. <div class="build-info-item-text">良</div>
  72. </div>
  73. <div
  74. class="build-info-item orange"
  75. :style="
  76. 'left: ' + circleXY[3].x + 'px; top: -' + circleXY[3].y + 'px'
  77. "
  78. >
  79. <div class="build-info-item-num">{{ csl }}</div>
  80. <div class="build-info-item-text">差</div>
  81. </div>
  82. <div
  83. class="build-info-item red"
  84. :style="
  85. 'left: ' + circleXY[4].x + 'px; top: -' + circleXY[4].y + 'px'
  86. "
  87. >
  88. <div class="build-info-item-num">{{ gzsl }}</div>
  89. <div class="build-info-item-text">故障</div>
  90. </div>
  91. </div>
  92. </div>
  93. </div>
  94. </template>
  95. <script>
  96. import loading from "@com/coms/loading/loading.vue";
  97. import * as THREE from "three";
  98. import { GLTFLoader } from "@node/three/examples/jsm/loaders/GLTFLoader.js";
  99. import { OrbitControls } from "@node/three/examples/jsm/controls/OrbitControls.js";
  100. import { GeometryUtils } from "@node/three/examples/jsm/utils/GeometryUtils.js";
  101. let camera, scene, renderer, controls;
  102. let mixers = [];
  103. let clock = new THREE.Clock();
  104. let fanAnimates = [];
  105. let fans = [];
  106. // let cylinder = null;
  107. export default {
  108. // 名称
  109. name: "ThreeModel1",
  110. // 使用组件
  111. components: {
  112. loading,
  113. },
  114. // 传入参数
  115. props: {
  116. data: {
  117. type: Object,
  118. default: () => {},
  119. },
  120. },
  121. // 自定义事件
  122. emits: {
  123. when: null,
  124. },
  125. // 数据
  126. data() {
  127. const that = this;
  128. return {
  129. mapSource: {},
  130. warScore: 60,
  131. pointer: false,
  132. jkd: null,
  133. ysl: null,
  134. lsl: null,
  135. csl: null,
  136. gzsl: null,
  137. htmlLayer: [
  138. {
  139. // 麻黄山
  140. id: "fan-mhs",
  141. x: 0,
  142. y: 0,
  143. show: true,
  144. ox: 50,
  145. oy: 10,
  146. fanW: 60,
  147. fanH: 100,
  148. fanX: 0,
  149. fanY: -110,
  150. position: null,
  151. name: "麻黄山",
  152. clickName: function () {
  153. that.clickMapItem([
  154. "MHS_FDC_SC",
  155. "MG01_01_TD",
  156. "MG01_02_TD",
  157. "MG01_03_TD",
  158. "MG01_04_TD",
  159. "MG01_05_TD",
  160. ]);
  161. },
  162. clickFan: () => {
  163. this.showFanInfo(this.htmlLayer[0].position);
  164. this.jkd = this.htmlLayer[0].jkd;
  165. this.ysl = this.htmlLayer[0].ysl;
  166. this.lsl = this.htmlLayer[0].lsl;
  167. this.csl = this.htmlLayer[0].csl;
  168. this.gzsl = this.htmlLayer[0].gzsl;
  169. that.clickMapItem([
  170. "MHS_FDC_SC",
  171. "MG01_01_TD",
  172. "MG01_02_TD",
  173. "MG01_03_TD",
  174. "MG01_04_TD",
  175. "MG01_05_TD",
  176. ]);
  177. },
  178. },
  179. {
  180. // 牛首山
  181. id: "fan-nss",
  182. x: 0,
  183. y: 0,
  184. show: true,
  185. ox: 35,
  186. oy: 0,
  187. fanW: 60,
  188. fanH: 100,
  189. fanX: 0,
  190. fanY: -110,
  191. position: null,
  192. name: "牛首山",
  193. clickName: function () {
  194. console.log("牛首山");
  195. that.clickMapItem([
  196. "NSS_FDC_SC",
  197. "NG01_27_TD",
  198. "NG01_28_TD",
  199. "NG01_30_TD",
  200. "NG01_31_TD",
  201. "NG01_32_TD",
  202. ]);
  203. },
  204. clickFan: () => {
  205. this.showFanInfo(this.htmlLayer[1].position);
  206. this.jkd = this.htmlLayer[1].jkd;
  207. this.ysl = this.htmlLayer[1].ysl;
  208. this.lsl = this.htmlLayer[1].lsl;
  209. this.csl = this.htmlLayer[1].csl;
  210. this.gzsl = this.htmlLayer[1].gzsl;
  211. that.clickMapItem([
  212. "NSS_FDC_SC",
  213. "NG01_27_TD",
  214. "NG01_28_TD",
  215. "NG01_30_TD",
  216. "NG01_31_TD",
  217. "NG01_32_TD",
  218. ]);
  219. },
  220. },
  221. {
  222. // 青山
  223. id: "fan-qs",
  224. x: 0,
  225. y: 0,
  226. show: true,
  227. ox: 60,
  228. oy: 10,
  229. fanW: 60,
  230. fanH: 100,
  231. fanX: 0,
  232. fanY: -100,
  233. position: null,
  234. name: "青山",
  235. clickName: function () {
  236. that.clickMapItem([
  237. "QS_FDC_SC",
  238. "QG01_67_TD",
  239. "QG01_69_TD",
  240. "QG01_71_TD",
  241. "QG01_72_TD",
  242. "QG01_73_TD",
  243. ]);
  244. },
  245. clickFan: () => {
  246. this.showFanInfo(this.htmlLayer[2].position);
  247. this.jkd = this.htmlLayer[2].jkd;
  248. this.ysl = this.htmlLayer[2].ysl;
  249. this.lsl = this.htmlLayer[2].lsl;
  250. this.csl = this.htmlLayer[2].csl;
  251. this.gzsl = this.htmlLayer[2].gzsl;
  252. that.clickMapItem([
  253. "QS_FDC_SC",
  254. "QG01_67_TD",
  255. "QG01_69_TD",
  256. "QG01_71_TD",
  257. "QG01_72_TD",
  258. "QG01_73_TD",
  259. ]);
  260. },
  261. },
  262. {
  263. // 石板泉
  264. id: "fan-sbq",
  265. x: 0,
  266. y: 0,
  267. show: true,
  268. ox: 40,
  269. oy: 0,
  270. fanW: 60,
  271. fanH: 90,
  272. fanX: 0,
  273. fanY: -90,
  274. position: null,
  275. name: "石板泉",
  276. clickName: function () {
  277. console.log("石板泉");
  278. that.clickMapItem([
  279. "SBQ_FDC_SC",
  280. "SG01_01_TD",
  281. "SG01_02_TD",
  282. "SG01_03_TD",
  283. "SG01_04_TD",
  284. "SG01_05_TD",
  285. ]);
  286. },
  287. clickFan: () => {
  288. this.showFanInfo(this.htmlLayer[3].position);
  289. this.jkd = this.htmlLayer[3].jkd;
  290. this.ysl = this.htmlLayer[3].ysl;
  291. this.lsl = this.htmlLayer[3].lsl;
  292. this.csl = this.htmlLayer[3].csl;
  293. this.gzsl = this.htmlLayer[3].gzsl;
  294. that.clickMapItem([
  295. "SBQ_FDC_SC",
  296. "SG01_01_TD",
  297. "SG01_02_TD",
  298. "SG01_03_TD",
  299. "SG01_04_TD",
  300. "SG01_05_TD",
  301. ]);
  302. },
  303. },
  304. {
  305. // 香山
  306. id: "fan-xs",
  307. x: 0,
  308. y: 0,
  309. show: true,
  310. ox: 25,
  311. oy: 20,
  312. fanW: 70,
  313. fanH: 100,
  314. fanX: -15,
  315. fanY: -110,
  316. position: null,
  317. name: "香山",
  318. clickName: function () {
  319. console.log("香山");
  320. that.clickMapItem([
  321. "XS_FDC_SC",
  322. "XG01_26_TD",
  323. "XG01_27_TD",
  324. "XG01_29_TD",
  325. "XG01_30_TD",
  326. "XG01_31_TD",
  327. ]);
  328. },
  329. clickFan: () => {
  330. this.showFanInfo(this.htmlLayer[4].position);
  331. this.jkd = this.htmlLayer[4].jkd;
  332. this.ysl = this.htmlLayer[4].ysl;
  333. this.lsl = this.htmlLayer[4].lsl;
  334. this.csl = this.htmlLayer[4].csl;
  335. this.gzsl = this.htmlLayer[4].gzsl;
  336. that.clickMapItem([
  337. "XS_FDC_SC",
  338. "XG01_26_TD",
  339. "XG01_27_TD",
  340. "XG01_29_TD",
  341. "XG01_30_TD",
  342. "XG01_31_TD",
  343. ]);
  344. },
  345. },
  346. {
  347. // 大武口
  348. id: "light-dwk",
  349. x: 0,
  350. y: 0,
  351. show: true,
  352. ox: 330,
  353. oy: 80,
  354. fanW: 60,
  355. fanH: 80,
  356. fanX: 0,
  357. fanY: -80,
  358. position: null,
  359. name: "大武口",
  360. clickName: function () {
  361. console.log("大武口");
  362. },
  363. clickFan: () => {
  364. console.log("大武口");
  365. },
  366. },
  367. {
  368. // 平罗
  369. id: "light-pl",
  370. x: 0,
  371. y: 0,
  372. show: true,
  373. ox: 330,
  374. oy: 80,
  375. fanW: 60,
  376. fanH: 80,
  377. fanX: 0,
  378. fanY: -80,
  379. position: null,
  380. name: "平罗",
  381. clickName: function () {
  382. console.log("平罗");
  383. },
  384. clickFan: () => {
  385. console.log("平罗");
  386. },
  387. },
  388. {
  389. // 马场湖
  390. id: "light-mch",
  391. x: 0,
  392. y: 0,
  393. show: true,
  394. ox: 230,
  395. oy: 80,
  396. fanW: 60,
  397. fanH: 80,
  398. fanX: 0,
  399. fanY: -80,
  400. position: null,
  401. name: "马场湖",
  402. clickName: function () {
  403. console.log("马场湖");
  404. },
  405. clickFan: () => {
  406. console.log("马场湖");
  407. },
  408. },
  409. {
  410. // 宣和
  411. id: "light-xh",
  412. x: 0,
  413. y: 0,
  414. show: true,
  415. ox: 260,
  416. oy: 90,
  417. fanW: 60,
  418. fanH: 80,
  419. fanX: 0,
  420. fanY: -80,
  421. position: null,
  422. name: "宣和",
  423. clickName: function () {
  424. console.log("宣和");
  425. },
  426. clickFan: () => {
  427. console.log("宣和");
  428. },
  429. },
  430. {
  431. // 海子井
  432. id: "light-hzj",
  433. x: 0,
  434. y: 0,
  435. show: true,
  436. ox: 290,
  437. oy: 120,
  438. fanW: 60,
  439. fanH: 80,
  440. fanX: 0,
  441. fanY: -80,
  442. position: null,
  443. name: "海子井",
  444. clickName: function () {
  445. console.log("海子井");
  446. },
  447. clickFan: () => {
  448. console.log("海子井");
  449. },
  450. },
  451. { // 大楼
  452. id: "build",
  453. x: 0,
  454. y: 0,
  455. show: true,
  456. ox: 290,
  457. oy: 120,
  458. fanW: 60,
  459. fanH: 100,
  460. fanX: -260,
  461. fanY: -200,
  462. position: null,
  463. name: "大楼",
  464. clickName: function () { console.log("大楼") },
  465. clickFan: () => {
  466. this.showFanInfo(this.htmlLayer[10].position);
  467. },
  468. },
  469. ],
  470. fanInfoLayer: {
  471. id: "fan-info",
  472. x: 0,
  473. y: 0,
  474. show: false,
  475. ox: 70,
  476. oy: -40,
  477. position: null,
  478. },
  479. circleXY: [
  480. {
  481. x: 130 * Math.cos((180 / 180) * Math.PI) - 33,
  482. y: 130 * Math.sin((180 / 180) * Math.PI) + 33,
  483. },
  484. {
  485. x: 130 * Math.cos((135 / 180) * Math.PI) - 33,
  486. y: 130 * Math.sin((135 / 180) * Math.PI) + 33,
  487. },
  488. {
  489. x: 130 * Math.cos((90 / 180) * Math.PI) - 33,
  490. y: 130 * Math.sin((90 / 180) * Math.PI) + 33,
  491. },
  492. {
  493. x: 130 * Math.cos((45 / 180) * Math.PI) - 33,
  494. y: 130 * Math.sin((45 / 180) * Math.PI) + 33,
  495. },
  496. {
  497. x: 130 * Math.cos((0 / 180) * Math.PI) - 33,
  498. y: 130 * Math.sin((0 / 180) * Math.PI) + 33,
  499. },
  500. ],
  501. colors: [
  502. { colorName: "green", state: "dj", stateName: "待机", color: 0x05bb4c },
  503. { colorName: "blue", state: "yx", stateName: "运行", color: 0x4b55ae },
  504. { colorName: "pink", state: "xd", stateName: "限电", color: 0xc531c7 },
  505. { colorName: "red", state: "gz", stateName: "故障", color: 0xba3237 },
  506. {
  507. colorName: "orange",
  508. state: "jx",
  509. stateName: "检修",
  510. color: 0xe17e23,
  511. },
  512. { colorName: "gray", state: "lx", stateName: "离线", color: 0x606769 },
  513. { colorName: "white", state: "sl", stateName: "受累", color: 0xffffff },
  514. ],
  515. fanName: "",
  516. playAnimation: true,
  517. mapDeg: 0,
  518. };
  519. },
  520. // 函数
  521. methods: {
  522. rinseData() {
  523. if (this.mapSource && this.mapSource.MHS_FDC) {
  524. for (let pKey in this.mapSource) {
  525. this.htmlLayer.forEach((ele) => {
  526. if (
  527. pKey.toLowerCase().indexOf(ele.id.toLowerCase().split("-")[1]) !==
  528. -1
  529. ) {
  530. for (let cKey in this.mapSource[pKey]) {
  531. ele[cKey] = this.mapSource[pKey][cKey];
  532. }
  533. }
  534. });
  535. }
  536. }
  537. },
  538. // 点击风场或者光伏传回点击的值
  539. clickMapItem(videoArray = []) {
  540. this.$emit("clickMapItem", videoArray);
  541. },
  542. // Vector3 to screen
  543. vector3ToScreen: function (position) {
  544. const centerX = this.$el.scrollWidth / 2;
  545. const centerY = this.$el.scrollHeight / 2;
  546. const v3 = new THREE.Vector3(position.x, position.y, position.z);
  547. const standardVec = v3.project(camera);
  548. const screenX = Math.round(centerX * standardVec.x + centerX);
  549. const screenY = Math.round(-centerY * standardVec.y + centerY);
  550. return { x: screenX, y: screenY };
  551. },
  552. // 场景
  553. initScene: function () {
  554. scene = new THREE.Scene();
  555. // scene.background = new THREE.Color(0xa0a0a0);
  556. },
  557. // 相机
  558. initCamera: function () {
  559. camera = new THREE.PerspectiveCamera(
  560. 45,
  561. this.$el.scrollWidth / this.$el.scrollHeight,
  562. 0.1,
  563. 10000
  564. );
  565. camera.position.set(50, 50, 50);
  566. },
  567. // 坐标轴
  568. initAxesHelper: function () {
  569. const axesHelper = new THREE.AxesHelper(150);
  570. scene.add(axesHelper);
  571. axesHelper.position.set(0, 0, 0);
  572. },
  573. // 渲染器
  574. initRender: function () {
  575. renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  576. // renderer.setClearAlpha(0);
  577. renderer.setSize(this.$el.scrollWidth, this.$el.scrollHeight);
  578. this.$el.append(renderer.domElement);
  579. },
  580. // 灯光
  581. initLight: function () {
  582. // let light = new THREE.PointLight(0xffffff, 2);
  583. let light = new THREE.AmbientLight(0xffffff, 5);
  584. // light.position.set(50, 50, 50);
  585. scene.add(light);
  586. },
  587. // 控制器
  588. initControls: function () {
  589. controls = new OrbitControls(camera, renderer.domElement);
  590. controls.enablePan = false;
  591. controls.maxPolarAngle = (45 / 180) * Math.PI;
  592. controls.minPolarAngle = (45 / 180) * Math.PI;
  593. controls.maxAzimuthAngle = (45 / 180) * Math.PI;
  594. controls.minAzimuthAngle = (45 / 180) * Math.PI;
  595. controls.enableKeys = false;
  596. controls.enableZoom = false;
  597. controls.update();
  598. // controls.maxAzimuthAngle = 50 / 180 * Math.PI;
  599. // controls.minAzimuthAngle = 40 / 180 * Math.PI;
  600. // controls.addEventListener("change", () => {
  601. // let cDeg = controls.getAzimuthalAngle() / Math.PI * 180;
  602. // this.mapDeg = cDeg - 40 - 5;
  603. // this.setEveryHTML();
  604. // });
  605. },
  606. // 初始化一个风机动画
  607. initFanAnimate: function (obj) {
  608. let fanAnimateObj = {
  609. speed: 0.05,
  610. rotate: 0,
  611. fan: obj.getObjectByName(`${obj.name}_leaf`),
  612. fan1: obj.getObjectByName(`${obj.name}_leaf_1`),
  613. fan2: obj.getObjectByName(`${obj.name}_leaf_2`),
  614. fan3: obj.getObjectByName(`${obj.name}_leaf_3`),
  615. };
  616. let fanAnimateFunction = function () {
  617. // fanAnimateObj.fan.rotateOnAxis(new THREE.Vector3(0, 1, 0), fanAnimateObj.speed);
  618. fanAnimateObj.fan.rotateY(fanAnimateObj.speed);
  619. // fanAnimateObj.fan1.rotateZ(fanAnimateObj.speed);
  620. // fanAnimateObj.fan2.rotateZ(fanAnimateObj.speed);
  621. // fanAnimateObj.fan3.rotateZ(fanAnimateObj.speed);
  622. // fanAnimateObj.rotate += fanAnimateObj.speed;
  623. // if (fanAnimateObj.rotate >= 360) {
  624. // fanAnimateObj.rotate = 0;
  625. // }
  626. fanAnimateObj.animateId =
  627. window.requestAnimationFrame(fanAnimateFunction);
  628. };
  629. fanAnimateObj.stop = function () {
  630. window.cancelAnimationFrame(fanAnimateObj.animateId);
  631. };
  632. fanAnimateObj.start = function () {
  633. fanAnimateFunction();
  634. };
  635. fanAnimateFunction();
  636. fanAnimates.push(fanAnimateObj);
  637. return fanAnimateObj;
  638. },
  639. // 清空风机动画
  640. clearFanAnimate: function () {
  641. while (fanAnimates.length > 0) {
  642. let fanAnimateObj = fanAnimates.shift();
  643. fanAnimateObj.stop();
  644. }
  645. this.playAnimation = false;
  646. },
  647. // 开始风机动画
  648. startFanAnimate: function () {
  649. fans.forEach((fan) => {
  650. this.initFanAnimate(fan);
  651. });
  652. this.playAnimation = true;
  653. },
  654. // 内容
  655. initContent: function () {
  656. // 加载3D地面
  657. let loaderGround = new GLTFLoader(); /*实例化加载器*/
  658. loaderGround.load(
  659. "static/3d/fanvar.gltf",
  660. (gltf) => {
  661. gltf.scene.position.set(17, 10, -12);
  662. // console.log(gltf);
  663. scene.add(gltf.scene);
  664. let rootNode = gltf.scene.children[0];
  665. let fan = gltf.scene.children[0].children[0];
  666. rootNode.remove(fan);
  667. this.setFanPosition(rootNode, fan);
  668. },
  669. (xhr) => {},
  670. function (error) {
  671. console.error("load error!" + error.getWebGLErrorMessage());
  672. }
  673. );
  674. loaderGround.load(
  675. "static/3d/light.gltf",
  676. (gltf) => {
  677. gltf.scene.position.set(17, 10, -12);
  678. // console.log(gltf);
  679. scene.add(gltf.scene);
  680. let rootNode = gltf.scene.children[0];
  681. let light = gltf.scene.children[0].children[0];
  682. rootNode.remove(light);
  683. this.setLightPosition(rootNode, light);
  684. },
  685. (xhr) => {},
  686. function (error) {
  687. console.error("load error!" + error.getWebGLErrorMessage());
  688. }
  689. );
  690. loaderGround.load(
  691. "static/3d/build.gltf",
  692. (gltf) => {
  693. gltf.scene.position.set(0, 0, -35); // 10, 0, -50 -6, 0.13, -50
  694. scene.add(gltf.scene);
  695. gltf.scene.rotateY(120);
  696. this.htmlLayer[10].position = {
  697. x: -6,
  698. y: -4,
  699. z: -35,
  700. }
  701. scene.onAfterRender = () => {
  702. this.$emit("when");
  703. this.$refs.pageLoading.hide();
  704. this.setEveryHTML();
  705. };
  706. },
  707. (xhr) => {},
  708. function (error) {
  709. console.error("load error!" + error.getWebGLErrorMessage());
  710. }
  711. );
  712. },
  713. // 改变元素底盘的颜色(带底盘的)
  714. changeObjectColor: function (obj, colorName) {
  715. for (let i = 1; i <= 4; i++) {
  716. let c = obj.getObjectByName(`${obj.name}_c_${i}`);
  717. c.material = new THREE.MeshBasicMaterial({
  718. color: 0xf0f0f0,
  719. opacity: 0.8,
  720. transparent: true,
  721. });
  722. }
  723. let leaf = obj.getObjectByName(`${obj.name}_leaf`);
  724. for (let chind of leaf.children) {
  725. chind.material = new THREE.MeshBasicMaterial({
  726. color: 0xf0f0f0,
  727. opacity: 0.8,
  728. transparent: true,
  729. });
  730. }
  731. let chassis = obj.getObjectByName(`${obj.name}_chassis`);
  732. // let color = this.colors.find(t => t.colorName == colorName || t.state == colorName || t.stateName == colorName).color;
  733. chassis.material = new THREE.MeshBasicMaterial({
  734. // color: color,
  735. color: 0xf0f0f0,
  736. opacity: 0.8,
  737. transparent: true,
  738. });
  739. },
  740. // 改变元素的颜色(光伏)
  741. changeElColor: function (obj, colorName) {
  742. // let color = this.colors.find(t => t.colorName == colorName || t.state == colorName || t.stateName == colorName).color;
  743. // for (let i = 1; i <= 6; i++) {
  744. // let el = obj.getObjectByName(`${obj.name}_item_${i}`);
  745. // el.material = new THREE.MeshBasicMaterial({
  746. // color: color,
  747. // opacity: 0.8,
  748. // transparent: true,
  749. // });
  750. // }
  751. },
  752. // 设置风机name
  753. setFanName: function (obj, name) {
  754. obj.name = name;
  755. obj.userData.name = name;
  756. // "Cylinder881" "Cone277"
  757. let Cylinder881 = obj.getObjectByName("Cylinder881");
  758. let Cylinder882 = obj.getObjectByName("Cylinder882");
  759. let Cylinder883 = obj.getObjectByName("Cylinder883");
  760. let Cone277 = obj.getObjectByName("Cone277");
  761. Cylinder881.name = `${name}_c_1`;
  762. Cylinder881.userData.name = `${name}_c_1`;
  763. Cylinder882.name = `${name}_c_2`;
  764. Cylinder882.userData.name = `${name}_c_2`;
  765. Cylinder883.name = `${name}_c_3`;
  766. Cylinder883.userData.name = `${name}_c_3`;
  767. Cone277.name = `${name}_c_4`;
  768. Cone277.userData.name = `${name}_c_4`;
  769. // "Box719")
  770. let fanBig1_1 = obj.getObjectByName("Box719");
  771. let fanBig1_2 = obj.getObjectByName("Box720");
  772. let fanBig1_3 = obj.getObjectByName("Box721");
  773. fanBig1_1.name = `${name}_leaf_1`;
  774. fanBig1_1.userData.name = `${name}_leaf_1`;
  775. fanBig1_2.name = `${name}_leaf_2`;
  776. fanBig1_2.userData.name = `${name}_leaf_2`;
  777. fanBig1_3.name = `${name}_leaf_3`;
  778. fanBig1_3.userData.name = `${name}_leaf_3`;
  779. obj.children[obj.children.length - 1].name = `${name}_chassis`;
  780. obj.children[obj.children.length - 1].userData.name = `${name}_chassis`;
  781. obj.remove(fanBig1_1, fanBig1_2, fanBig1_3);
  782. var group = new THREE.Group();
  783. // var group = new THREE.Mesh(new THREE.BoxGeometry(0.01, 0.01, 0.01),
  784. // new THREE.MeshBasicMaterial({
  785. // color: 0xff0000
  786. // })
  787. // );
  788. group.position.set(
  789. group.position.x + 0.0067,
  790. group.position.y,
  791. group.position.z + 0.01
  792. );
  793. fanBig1_1.position.set(
  794. fanBig1_1.position.x - 0.0067,
  795. fanBig1_1.position.y,
  796. fanBig1_1.position.z - 0.01
  797. );
  798. fanBig1_2.position.set(
  799. fanBig1_2.position.x - 0.0067,
  800. fanBig1_2.position.y,
  801. fanBig1_2.position.z - 0.01
  802. );
  803. fanBig1_3.position.set(
  804. fanBig1_3.position.x - 0.0067,
  805. fanBig1_3.position.y,
  806. fanBig1_3.position.z - 0.01
  807. );
  808. group.add(fanBig1_1, fanBig1_2, fanBig1_3);
  809. obj.add(group);
  810. group.name = `${name}_leaf`;
  811. },
  812. // 设置风机位置 颜色
  813. setFanPosition: function (rootNode, obj) {
  814. // 麻黄山
  815. let fan_mhs = obj.clone(true);
  816. fan_mhs.position.set(10, 5, -4);
  817. this.setFanName(fan_mhs, "fan_mhs");
  818. rootNode.add(fan_mhs);
  819. this.htmlLayer[0].position = fan_mhs.position;
  820. if (this.htmlLayer[0].jkd < this.warScore) {
  821. this.initCylinderGeometry(fan_mhs);
  822. }
  823. // 牛首山
  824. let fan_nss = obj.clone(true);
  825. fan_nss.position.set(-4, 4, -15);
  826. this.setFanName(fan_nss, "fan_nss");
  827. rootNode.add(fan_nss);
  828. this.htmlLayer[1].position = fan_nss.position;
  829. if (this.htmlLayer[1].jkd < this.warScore) {
  830. this.initCylinderGeometry(fan_nss); // 故障 显示红色圆柱
  831. }
  832. // 青山
  833. let fan_qs = obj.clone(true);
  834. fan_qs.position.set(15, 5, -9);
  835. this.setFanName(fan_qs, "fan_qs");
  836. rootNode.add(fan_qs);
  837. this.htmlLayer[2].position = fan_qs.position;
  838. if (this.htmlLayer[2].jkd < this.warScore) {
  839. this.initCylinderGeometry(fan_qs);
  840. }
  841. // 石板泉
  842. let fan_sbq = obj.clone(true);
  843. fan_sbq.position.set(4, 5, -2);
  844. this.setFanName(fan_sbq, "fan_sbq");
  845. rootNode.add(fan_sbq);
  846. this.htmlLayer[3].position = fan_sbq.position;
  847. if (this.htmlLayer[3].jkd < this.warScore) {
  848. this.initCylinderGeometry(fan_sbq);
  849. }
  850. // 香山
  851. let fan_xs = obj.clone(true);
  852. fan_xs.position.set(-9, 7, 21);
  853. this.setFanName(fan_xs, "fan_xs");
  854. rootNode.add(fan_xs);
  855. this.htmlLayer[4].position = fan_xs.position;
  856. if (this.htmlLayer[4].jkd < this.warScore) {
  857. this.initCylinderGeometry(fan_xs);
  858. }
  859. // 改一下颜色
  860. this.changeObjectColor(fan_mhs, "green"); // 麻黄山
  861. this.changeObjectColor(fan_nss, "xd"); // 牛首山
  862. this.changeObjectColor(fan_qs, "检修"); // 青山
  863. this.changeObjectColor(fan_sbq, "white"); // 石板泉
  864. this.changeObjectColor(fan_xs, "运行"); // 香山
  865. // 风机存入数组
  866. fans.push(fan_mhs);
  867. fans.push(fan_nss);
  868. fans.push(fan_qs);
  869. fans.push(fan_sbq);
  870. fans.push(fan_xs);
  871. // 开始风机动画
  872. this.startFanAnimate();
  873. // 设置位置
  874. this.setEveryHTML();
  875. },
  876. // 设置光伏name
  877. setLightName: function (obj, name) {
  878. obj.name = name;
  879. obj.userData.name = name;
  880. let Box661 = obj.getObjectByName("Box661");
  881. let Box668 = obj.getObjectByName("Box668");
  882. let Box669 = obj.getObjectByName("Box669");
  883. let Box664 = obj.getObjectByName("Box664");
  884. let Box667 = obj.getObjectByName("Box667");
  885. let Box670 = obj.getObjectByName("Box670");
  886. Box661.name = `${name}_item_1`;
  887. Box661.userData.name = `${name}_item_1`;
  888. Box668.name = `${name}_item_2`;
  889. Box668.userData.name = `${name}_item_2`;
  890. Box669.name = `${name}_item_3`;
  891. Box669.userData.name = `${name}_item_3`;
  892. Box664.name = `${name}_item_4`;
  893. Box664.userData.name = `${name}_item_4`;
  894. Box667.name = `${name}_item_5`;
  895. Box667.userData.name = `${name}_item_5`;
  896. Box670.name = `${name}_item_6`;
  897. Box670.userData.name = `${name}_item_6`;
  898. },
  899. // 设置光伏位置 颜色
  900. setLightPosition: function (rootNode, obj) {
  901. obj.scale.set(200, 200, 200);
  902. // let Box660 = obj.getObjectByName("Box660");
  903. // let Box666 = obj.getObjectByName("Box666");
  904. // let Box662 = obj.getObjectByName("Box662");
  905. // let Box663 = obj.getObjectByName("Box663");
  906. // let Box659 = obj.getObjectByName("Box659");
  907. // let Box665 = obj.getObjectByName("Box665");
  908. // obj.remove(Box660);
  909. // obj.remove(Box666);
  910. // obj.remove(Box662);
  911. // obj.remove(Box663);
  912. // obj.remove(Box659);
  913. // obj.remove(Box665);
  914. // 大武口
  915. let light_dwk = obj.clone(true);
  916. light_dwk.position.set(-22, 0, -28); // -20 +30 // -2 0 -58 // -6, 0.13, -50 10, 0, -50
  917. // this.setLightName(light_dwk, "light_dwk");
  918. rootNode.add(light_dwk);
  919. this.htmlLayer[5].position = light_dwk.position;
  920. // 平罗
  921. let light_pl = obj.clone(true);
  922. light_pl.position.set(-26, 0.13, -25);
  923. // this.setLightName(light_pl, "light_pl");
  924. rootNode.add(light_pl);
  925. this.htmlLayer[6].position = light_pl.position;
  926. // 马场湖
  927. let light_mch = obj.clone(true);
  928. light_mch.position.set(-47, 2, 0);
  929. // this.setLightName(light_mch, "light_mch");
  930. rootNode.add(light_mch);
  931. this.htmlLayer[7].position = light_mch.position;
  932. // 宣和
  933. let light_xh = obj.clone(true);
  934. light_xh.position.set(-38, 4, 3);
  935. // this.setLightName(light_xh, "light_xh");
  936. rootNode.add(light_xh);
  937. this.htmlLayer[8].position = light_xh.position;
  938. // 海子井
  939. let light_hzj = obj.clone(true);
  940. light_hzj.position.set(-30, 2, 4);
  941. // this.setLightName(light_hzj, "light_hzj");
  942. rootNode.add(light_hzj);
  943. this.htmlLayer[9].position = light_hzj.position;
  944. // 改变颜色
  945. this.changeElColor(light_dwk, "green"); // 大武口
  946. this.changeElColor(light_pl, "离线"); // 平罗
  947. this.changeElColor(light_mch, "yx"); // 马场湖
  948. this.changeElColor(light_xh, "red"); // 宣和
  949. this.changeElColor(light_hzj, "受累"); // 海子井
  950. // 设置位置
  951. this.setEveryHTML();
  952. },
  953. // 创建一个圆柱
  954. initCylinderGeometry: function (obj, mode = "fan") {
  955. let cr = 1.7;
  956. let xyz = [5.75, 6.85, -0.63];
  957. switch (mode) {
  958. case "fan":
  959. cr = 1.7;
  960. xyz = [5.75, 6.85, -0.63];
  961. break;
  962. case "light":
  963. cr = 3;
  964. xyz = [5.75, 6.85, -0.63];
  965. break;
  966. default:
  967. break;
  968. }
  969. let geometry = new THREE.CylinderGeometry(cr, cr, 4, 128, 1, true);
  970. //加载纹理
  971. let texture = new THREE.TextureLoader().load(
  972. "static/3d/beam-texture-red.png"
  973. );
  974. texture.wrapS = texture.wrapT = THREE.RepeatWrapping; //每个都重复
  975. texture.repeat.set(1, 1);
  976. texture.needsUpdate = true;
  977. let materials = [
  978. //圆柱侧面材质,使用纹理贴图
  979. new THREE.MeshBasicMaterial({
  980. map: texture,
  981. side: THREE.FrontSide,
  982. transparent: true,
  983. }),
  984. //圆柱顶材质
  985. new THREE.MeshBasicMaterial({
  986. transparent: true,
  987. opacity: 0,
  988. side: THREE.DoubleSide,
  989. // side: THREE.DoubleSide,THREE.FrontSide THREE.BackSide
  990. }),
  991. //圆柱底材质
  992. new THREE.MeshBasicMaterial({
  993. transparent: true,
  994. opacity: 0,
  995. side: THREE.DoubleSide,
  996. }),
  997. ];
  998. let cylinder = new THREE.Mesh(geometry, materials);
  999. cylinder.position.set(
  1000. obj.position.x + xyz[0],
  1001. obj.position.y + xyz[1],
  1002. obj.position.z + xyz[2]
  1003. );
  1004. scene.add(cylinder);
  1005. },
  1006. // 设置每一个html的位置
  1007. setEveryHTML: function () {
  1008. this.htmlLayer.forEach((value) => {
  1009. if (value.show && value.position) {
  1010. const screen = this.vector3ToScreen(value.position);
  1011. value.x = screen.x + value.ox;
  1012. value.y = screen.y + value.oy;
  1013. }
  1014. });
  1015. },
  1016. // 判断时候是可点击对象 返回null 或object
  1017. getClickObject: function (intersects) {
  1018. const names = ["fan"];
  1019. const namesRoot = ["fanvar"];
  1020. let x = true;
  1021. let obj = null;
  1022. for (let intersect of intersects) {
  1023. let temObj = intersect.object;
  1024. while (x) {
  1025. for (let name of names) {
  1026. if (temObj.name.indexOf(`${name}_`) == 0) {
  1027. obj = temObj;
  1028. x = false;
  1029. break;
  1030. }
  1031. }
  1032. if (x && temObj.parent) {
  1033. temObj = temObj.parent;
  1034. continue;
  1035. }
  1036. break;
  1037. }
  1038. }
  1039. if (obj) {
  1040. while (obj.parent && !namesRoot.includes(obj.parent.name)) {
  1041. obj = obj.parent;
  1042. }
  1043. }
  1044. if (obj && !namesRoot.includes(obj.parent.name)) {
  1045. obj = null;
  1046. }
  1047. return obj;
  1048. },
  1049. // 显示风机弹出层
  1050. showFanInfo: function (position) {
  1051. this.fanInfoLayer.position = {
  1052. x: position.x,
  1053. y: position.y,
  1054. z: position.z,
  1055. };
  1056. const screen = this.vector3ToScreen(this.fanInfoLayer.position);
  1057. this.fanInfoLayer.x = screen.x + this.fanInfoLayer.ox;
  1058. this.fanInfoLayer.y = screen.y + this.fanInfoLayer.oy;
  1059. this.fanInfoLayer.show = true;
  1060. },
  1061. // 隐藏风机弹出层
  1062. hideFanInfo: function () {
  1063. this.fanInfoLayer.show = false;
  1064. },
  1065. // 初始化云
  1066. initCloud: function () {},
  1067. // 初始化
  1068. initThree: function () {
  1069. this.initScene();
  1070. // this.initAxesHelper();
  1071. this.initCamera();
  1072. this.initRender();
  1073. this.initLight();
  1074. this.initControls();
  1075. this.initContent();
  1076. this.initCloud();
  1077. renderer.setAnimationLoop(this.animate);
  1078. },
  1079. // 动画
  1080. animate: function () {
  1081. renderer.render(scene, camera);
  1082. },
  1083. },
  1084. // 生命周期钩子
  1085. beforeCreate() {
  1086. // 创建前
  1087. },
  1088. created() {
  1089. // 创建后
  1090. },
  1091. beforeMount() {
  1092. // 渲染前
  1093. },
  1094. mounted() {
  1095. // 渲染后
  1096. this.initThree();
  1097. this.mapSource = this.data;
  1098. this.rinseData();
  1099. },
  1100. beforeUpdate() {
  1101. // 数据更新前
  1102. },
  1103. updated() {
  1104. // 数据更新后
  1105. },
  1106. beforeUnmount() {
  1107. // 销毁前
  1108. renderer.setAnimationLoop(null);
  1109. camera = null;
  1110. scene = null;
  1111. renderer = null;
  1112. },
  1113. watch: {
  1114. data(res) {
  1115. this.mapSource = res;
  1116. this.rinseData();
  1117. },
  1118. },
  1119. };
  1120. </script>
  1121. <style lang="less" scoped>
  1122. .three-model {
  1123. position: relative;
  1124. overflow: hidden;
  1125. // left: -112px;
  1126. .map-3d {
  1127. position: absolute;
  1128. width: 1118px;
  1129. height: 678px;
  1130. left: calc(50% - 559px);
  1131. top: calc(50% - 339px);
  1132. z-index: -1;
  1133. map {
  1134. width: 100%;
  1135. height: 100%;
  1136. }
  1137. }
  1138. .three-html-layer {
  1139. .three-html-dom {
  1140. position: absolute;
  1141. }
  1142. .fan-name {
  1143. display: inline-block;
  1144. height: 22px;
  1145. background: #222632;
  1146. border: 1px solid #646464;
  1147. border-radius: 10px;
  1148. padding: 0 10px;
  1149. font-size: 12px;
  1150. font-weight: 400;
  1151. color: #ffffff;
  1152. line-height: 22px;
  1153. }
  1154. .fan-can-click {
  1155. position: absolute;
  1156. background: #ff0000;
  1157. opacity: 0;
  1158. cursor: pointer;
  1159. }
  1160. .build-info {
  1161. width: 166px;
  1162. height: 33px;
  1163. // position: relative;
  1164. .build-info-close {
  1165. position: absolute;
  1166. width: 40px;
  1167. height: 40px;
  1168. top: -20px;
  1169. left: -20px;
  1170. border-radius: 50%;
  1171. background: #1a1f2fd8;
  1172. border: 1px solid #05bb4c;
  1173. box-shadow: 0px 8px 17px 1px #05bb4c66;
  1174. cursor: pointer;
  1175. color: @gray-l;
  1176. transition: all 0.3s;
  1177. display: flex;
  1178. align-items: center;
  1179. justify-content: center;
  1180. font-size: 14px;
  1181. &:hover {
  1182. color: #05bb4c;
  1183. }
  1184. }
  1185. .build-info-item {
  1186. width: 66px;
  1187. height: 66px;
  1188. border-radius: 50%;
  1189. background: #1a1f2fe5;
  1190. position: absolute;
  1191. &::after {
  1192. content: "";
  1193. position: absolute;
  1194. width: 44px;
  1195. height: 2px;
  1196. left: calc(50% - 22px);
  1197. top: calc(50% - 1px);
  1198. }
  1199. .build-info-item-num {
  1200. font-size: 22px;
  1201. font-weight: 500;
  1202. color: #ffffff;
  1203. text-align: center;
  1204. line-height: 1.2;
  1205. margin-top: 6px;
  1206. margin-bottom: 3px;
  1207. }
  1208. .build-info-item-text {
  1209. font-size: 12px;
  1210. color: #ffffff;
  1211. font-weight: 400;
  1212. text-align: center;
  1213. }
  1214. &.blue {
  1215. border: 2px solid #1da0d7;
  1216. &::after {
  1217. background: #1da0d7;
  1218. }
  1219. }
  1220. &.red {
  1221. border: 2px solid #ff0000;
  1222. &::after {
  1223. background: #ff0000;
  1224. }
  1225. }
  1226. &.green {
  1227. border: 2px solid #05bb4c;
  1228. &::after {
  1229. background: #05bb4c;
  1230. }
  1231. }
  1232. &.purple {
  1233. border: 2px solid #323e6f;
  1234. &::after {
  1235. background: #323e6f;
  1236. }
  1237. }
  1238. &.orange {
  1239. border: 2px solid #db5520;
  1240. &::after {
  1241. background: #db5520;
  1242. }
  1243. }
  1244. &.yellow {
  1245. border: 2px solid #edb32f;
  1246. &::after {
  1247. background: #edb32f;
  1248. }
  1249. }
  1250. }
  1251. }
  1252. }
  1253. }
  1254. </style>