index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. <template>
  2. <div class="map-container">
  3. <div id="map" class="map"></div>
  4. <!-- 以下为悬浮信息等 -->
  5. <div
  6. v-if="hoverInfo"
  7. :style="hoverStyle"
  8. class="hover-info"
  9. @mouseenter="hovering = true"
  10. @mouseleave="onHoverLeave"
  11. >
  12. <h3>{{ hoverInfo.fieldName }}</h3>
  13. <div>
  14. <p>
  15. <span>状态:</span
  16. ><span>{{
  17. hoverInfo.analysisState === 30 ? "已完成分析" : "未完成分析"
  18. }}</span>
  19. </p>
  20. <p>
  21. <span>风场编号</span><span>{{ hoverInfo.codeNumber }}</span>
  22. </p>
  23. <p>
  24. <span>风机数量</span><span>{{ newWind.engineTotalCount }} 台</span>
  25. </p>
  26. <p>
  27. <span>总容量</span
  28. ><span
  29. >{{
  30. newWind?.ratedCapacityNumber ? newWind.ratedCapacityNumber : 0
  31. }}/KW</span
  32. >
  33. </p>
  34. <p>
  35. <span>经度</span><span>{{ hoverInfo.longitude }}°</span>
  36. </p>
  37. <p>
  38. <span>纬度</span><span>{{ hoverInfo.latitude }}°</span>
  39. </p>
  40. <p>
  41. <span>分析模型数</span
  42. ><span
  43. >{{
  44. hoverInfo.analysisTypeCount ? hoverInfo.analysisTypeCount : 0
  45. }}个</span
  46. >
  47. </p>
  48. <p>
  49. <span>分析开始时间</span>
  50. <span>{{
  51. newWind.analysisStartTime
  52. ? $formatDateTWO(newWind.analysisStartTime)
  53. : "暂无数据"
  54. }}</span>
  55. </p>
  56. <p>
  57. <span>分析结束时间</span>
  58. <span>{{
  59. newWind.analysisEndTime
  60. ? $formatDateTWO(newWind.analysisEndTime)
  61. : "暂无数据"
  62. }}</span>
  63. </p>
  64. <p>
  65. <span>数据开始时间</span>
  66. <span>{{
  67. newWind.dataStartTime
  68. ? $formatDateTWO(newWind.dataStartTime)
  69. : "暂无数据"
  70. }}</span>
  71. </p>
  72. <p>
  73. <span>数据结束时间</span>
  74. <span>{{
  75. newWind.dataEndTime
  76. ? $formatDateTWO(newWind.dataEndTime)
  77. : "暂无数据"
  78. }}</span>
  79. </p>
  80. </div>
  81. </div>
  82. <div
  83. v-if="hoverfengji"
  84. :style="hoverfengjiStyle"
  85. class="hover-fengji"
  86. @mouseenter="hoveringFengji = true"
  87. @mouseleave="onFengjiLeave"
  88. >
  89. <h3>{{ hoverfengji.engineName }}</h3>
  90. <div>
  91. <p>
  92. <span>风机编号</span><span>{{ hoverfengji.engineCode }}</span>
  93. </p>
  94. <p>
  95. <span>额定容量</span
  96. ><span
  97. >{{
  98. hoverfengji?.ratedCapacity ? hoverfengji?.ratedCapacity : 0
  99. }}/KW</span
  100. >
  101. </p>
  102. <p>
  103. <span>海拔高度</span
  104. ><span>{{ hoverfengji.elevationHeight }} /米</span>
  105. </p>
  106. <p>
  107. <span>轮毂高度</span><span>{{ hoverfengji.hubHeight }} /米</span>
  108. </p>
  109. <p>
  110. <span>经度</span><span>{{ hoverfengji.longitude }}</span>
  111. </p>
  112. <p>
  113. <span>维度</span><span>{{ hoverfengji.latitude }}</span>
  114. </p>
  115. <p>
  116. 是否标杆风机
  117. <span>{{ hoverfengji.sightcing == 1 ? "是" : "否" }}</span>
  118. </p>
  119. <p>
  120. <span>额定风速</span><span>{{ hoverfengji.ratedWindSpeed }} m/s</span>
  121. </p>
  122. <p>
  123. <span>切入风速</span
  124. ><span>{{ hoverfengji.ratedCutInWindspeed }} m/s</span>
  125. </p>
  126. <p>
  127. <span>切出风速</span
  128. ><span>{{ hoverfengji.ratedCutOutWindspeed }} m/s</span>
  129. </p>
  130. </div>
  131. </div>
  132. <div
  133. v-if="hoverta"
  134. :style="hovertaStyle"
  135. class="hover-ta"
  136. @mouseenter="hoveringTa = true"
  137. @mouseleave="onTaLeave"
  138. >
  139. <h3>{{ hoverta.anemometerName }}</h3>
  140. <div>
  141. <p>
  142. <span>测风塔编号</span><span>{{ hoverta.anemometerCode }}</span>
  143. </p>
  144. <p>
  145. <span>经度</span><span>{{ hoverta.longitude }}</span>
  146. </p>
  147. <p>
  148. <span>纬度</span><span>{{ hoverta.latitude }}</span>
  149. </p>
  150. <p>
  151. <span>测风塔高度</span
  152. ><span>{{ hoverta.anemometerHeightStrings }}/米</span>
  153. </p>
  154. </div>
  155. </div>
  156. <!-- 异常描述弹窗 -->
  157. <el-dialog title="异常描述" :visible.sync="dialogVisible" width="50%">
  158. <el-table :data="tableData" max-height="500" style="width: 100%">
  159. <el-table-column prop="analysisTypeName" label="类型" width="300">
  160. </el-table-column>
  161. <el-table-column prop="errDesc" label="描述"> </el-table-column>
  162. </el-table>
  163. </el-dialog>
  164. </div>
  165. </template>
  166. <script>
  167. import "ol/ol.css";
  168. import { Map, View, Feature } from "ol";
  169. import TileLayer from "ol/layer/Tile.js";
  170. import { XYZ } from "ol/source";
  171. import { fromLonLat } from "ol/proj";
  172. import { Vector } from "ol/source";
  173. import { Vector as VectorLayer } from "ol/layer";
  174. import { Point } from "ol/geom";
  175. import { Icon, Style, Stroke, Fill } from "ol/style";
  176. import ZoomSlider from "ol/control/ZoomSlider.js";
  177. import { defaults as defaultControls } from "ol/control.js";
  178. import GeoJSON from "ol/format/GeoJSON";
  179. // 假设你已经有正确的山西省边界数据文件(GeoJSON 格式)
  180. import shanxiBoundary from "./shanxi.json";
  181. import icon01 from "../../assets/img/iconFC.png";
  182. import icon02 from "../../assets/img/iconFC.png";
  183. import icon03 from "../../assets/img/iconFC.png";
  184. import icon04 from "../../assets/img/iconFJ.png";
  185. import icon05 from "../../assets/img/icon05.png";
  186. import icon06 from "../../assets/img/iconFJ.png";
  187. import defaultIcon from "../../assets/img/iconFJ.png";
  188. // import icon01 from "../../assets/img/icon01.png";
  189. // import icon02 from "../../assets/img/icon02.png";
  190. // import icon03 from "../../assets/img/icon03.png";
  191. // import icon04 from "../../assets/img/icon04.png";
  192. // import icon05 from "../../assets/img/icon05.png";
  193. // import icon06 from "../../assets/img/iconFJ.png";
  194. // import defaultIcon from "../../assets/img/iconFJ.png";
  195. import { queryErrDescByEngine, queryFloatingWindowInfo } from "@/api/ledger.js";
  196. import { login } from "@/api/login";
  197. export default {
  198. props: {
  199. windEngineGroupByFieldCodeDetail: {
  200. type: Object,
  201. default: () => ({}),
  202. },
  203. },
  204. name: "T-map",
  205. data() {
  206. return {
  207. dialogVisible: false,
  208. tableData: [],
  209. hoverInfo: null,
  210. hovering: false,
  211. hideTimer: null,
  212. hoverStyle: {
  213. position: "absolute",
  214. left: "0px",
  215. top: "0px",
  216. },
  217. hoverfengji: null,
  218. hoverfengjiStyle: {
  219. position: "absolute",
  220. left: "0px",
  221. top: "0px",
  222. },
  223. hoverta: null,
  224. hovertaStyle: {
  225. position: "absolute",
  226. left: "0px",
  227. top: "0px",
  228. },
  229. hoveringFengji: false,
  230. hoveringTa: false,
  231. fengjiTimer: null,
  232. taTimer: null,
  233. newWind: {},
  234. };
  235. },
  236. mounted() {
  237. this.map = new Map({
  238. target: "map",
  239. view: new View({
  240. projection: "EPSG:4326",
  241. center: fromLonLat([116.389, 39.903]),
  242. zoom: 3,
  243. minZoom: 3,
  244. maxZoom: 15,
  245. extent: [69, 18, 136, 54],
  246. }),
  247. layers: [
  248. new TileLayer({
  249. source: new XYZ({
  250. // url: "http://106.120.102.238:18000/tiles/{z}/{x}/{y}.png", //外网
  251. // url: "http://127.0.0.1:8010/tiles/{z}/{x}/{y}.png", //本地
  252. // url: "http://192.168.50.235/tiles/{z}/{x}/{y}.png", //内网
  253. // url: "http://10.96.137.5:9080/tiles/{z}/{x}/{y}.png", //大~#@唐
  254. url: "http://192.168.0.1/tiles/{z}/{x}/{y}.png", //华电
  255. // url: "http://192.168.50.235/tiles/{z}/{x}/{y}.png", //中广核
  256. }),
  257. }),
  258. new VectorLayer({
  259. id: "marker",
  260. source: new Vector(),
  261. }),
  262. ],
  263. controls: defaultControls().extend([new ZoomSlider()]),
  264. });
  265. // -------------【添加山西省真实边界线】------------- 定位全国的时候注释这个代码
  266. // if (this.$route.path === "/home/cockpitManage") {
  267. // // 通过导入的 JSON 文件加载边界数据(GeoJSON 格式),并解析成矢量要素
  268. // const shanxiSource = new Vector({
  269. // features: new GeoJSON().readFeatures(shanxiBoundary, {
  270. // featureProjection: "EPSG:4326",
  271. // }),
  272. // });
  273. // // 创建矢量图层,仅显示边界线(填充颜色设为透明)
  274. // const shanxiLayer = new VectorLayer({
  275. // source: shanxiSource,
  276. // style: new Style({
  277. // stroke: new Stroke({
  278. // color: "rgba(59, 130, 246, 0.5)",
  279. // width: 3,
  280. // }),
  281. // fill: new Fill({
  282. // color: "rgba(59, 130, 246, 0.05)", // 透明填充
  283. // }),
  284. // }),
  285. // });
  286. // this.map.addLayer(shanxiLayer);
  287. // const markerLayer = this.map
  288. // .getLayers()
  289. // .getArray()
  290. // .find((layer) => {
  291. // return layer.get("id") === "marker";
  292. // });
  293. // if (markerLayer) {
  294. // markerLayer.setZIndex(10);
  295. // }
  296. // // 同时设置高亮图层 zIndex 较低
  297. // shanxiLayer.setZIndex(1);
  298. // // -------------【结束】-------------
  299. // const targetExtent = [106.8, 34.3, 118.6, 41.2];
  300. // this.map.getView().fit(targetExtent, { duration: 2000 });
  301. // }
  302. // 这个放在外面
  303. this.initEvent();
  304. },
  305. methods: {
  306. onHoverLeave() {
  307. this.hovering = false;
  308. clearTimeout(this.hideTimer);
  309. this.hideTimer = setTimeout(() => {
  310. if (!this.hovering) {
  311. this.hoverInfo = null;
  312. }
  313. }, 100); // 这里延迟100ms防止意外移出
  314. },
  315. onFengjiLeave() {
  316. this.hoveringFengji = false;
  317. clearTimeout(this.fengjiTimer);
  318. this.fengjiTimer = setTimeout(() => {
  319. if (!this.hoveringFengji) {
  320. this.hoverfengji = null;
  321. }
  322. }, 100); // 你可以根据实际需要调大一点时间
  323. },
  324. onTaLeave() {
  325. this.hoveringTa = false;
  326. clearTimeout(this.taTimer);
  327. this.taTimer = setTimeout(() => {
  328. if (!this.hoveringTa) {
  329. this.hoverta = null;
  330. }
  331. }, 100);
  332. },
  333. addMarker(data = { point: [120.2, 30.35], val: "1" }) {
  334. const layer = this.map
  335. .getLayers()
  336. .getArray()
  337. .find((element) => {
  338. return element.get("id") === "marker";
  339. });
  340. const source = layer.getSource();
  341. const iconSrc = this.getIconForValue(data.val);
  342. const scale = data.val === "4" ? [0.5, 0.5] : [0.3, 0.3];
  343. const feature = new Feature({
  344. geometry: new Point(fromLonLat(data.point, "EPSG:4326")),
  345. name: "marker",
  346. data,
  347. });
  348. feature.setStyle(
  349. new Style({
  350. image: new Icon({
  351. src: iconSrc,
  352. scale: scale,
  353. anchor: [0.5, 1],
  354. opacity: 1,
  355. }),
  356. })
  357. );
  358. source.addFeature(feature);
  359. },
  360. clearMarkers() {
  361. const layer = this.map
  362. .getLayers()
  363. .getArray()
  364. .find((element) => {
  365. return element.get("id") === "marker";
  366. });
  367. if (layer) {
  368. const source = layer.getSource();
  369. source.clear();
  370. }
  371. },
  372. getIconForValue(val) {
  373. switch (val) {
  374. case "-1":
  375. case -1:
  376. return icon01;
  377. case "10":
  378. case 10:
  379. return icon01;
  380. case 0:
  381. case "0":
  382. return icon02;
  383. case 20:
  384. case "20":
  385. return icon02;
  386. case 1:
  387. case "1":
  388. return icon03;
  389. case 30:
  390. case "30":
  391. return icon03;
  392. case "4":
  393. return icon04;
  394. case "5":
  395. return icon05;
  396. case "6":
  397. return icon06;
  398. default:
  399. return defaultIcon;
  400. }
  401. },
  402. initEvent() {
  403. let lastHoveredFeature = null;
  404. const calculateHoverTop = (mouseY, hoverHeight) => {
  405. const componentHeight = this.$el.clientHeight;
  406. const offset = 10;
  407. if (mouseY + hoverHeight + offset > componentHeight) {
  408. return mouseY - hoverHeight - offset;
  409. }
  410. return mouseY - hoverHeight / 2;
  411. };
  412. const calculateHoverLeft = (mouseX, hoverWidth) => {
  413. const componentWidth = this.$el.clientWidth;
  414. const offset = 10;
  415. if (mouseX + hoverWidth + offset > componentWidth) {
  416. return mouseX - hoverWidth - offset;
  417. }
  418. if (mouseX - hoverWidth - offset < 0) {
  419. return offset;
  420. }
  421. return mouseX + offset;
  422. };
  423. this.map.on("pointermove", (evt) => {
  424. const features = this.map.getFeaturesAtPixel(evt.pixel, {
  425. hitTolerance: 1,
  426. });
  427. if (features && features.length > 0) {
  428. const feature = features.at(0);
  429. const data = feature.get("data");
  430. // 如果没有 data 属性,则不处理,但允许事件冒泡
  431. if (!data) {
  432. return;
  433. }
  434. const val = data.val;
  435. // 如果当前悬停的 feature 与上一次不同,则处理
  436. if (lastHoveredFeature !== feature) {
  437. const hoverHeight = 250;
  438. const hoverWidth = 200;
  439. const topPosition = calculateHoverTop(evt.pixel[1], hoverHeight);
  440. const leftPosition = calculateHoverLeft(evt.pixel[0], hoverWidth);
  441. if (val == "1" || val == "30" || val == "-1" || val == "10") {
  442. this.hoverInfo = data;
  443. this.hoverStyle.left = `${leftPosition}px`;
  444. this.hoverStyle.top = `${topPosition}px`;
  445. this.getwind();
  446. } else if (val == "4") {
  447. this.hoverfengji = data;
  448. this.hoverfengjiStyle.left = `${leftPosition}px`;
  449. this.hoverfengjiStyle.top = `${topPosition}px`;
  450. this.currentFeatureData = data;
  451. } else if (val == "5") {
  452. this.hoverta = data;
  453. this.hovertaStyle.left = `${leftPosition}px`;
  454. this.hovertaStyle.top = `${topPosition}px`;
  455. this.currentFeatureData = data;
  456. } else if (val == "6") {
  457. this.currentFeatureData = data;
  458. } else {
  459. this.hoverInfo = false;
  460. this.hoverfengji = false;
  461. this.hoverta = false;
  462. this.currentFeatureData = false;
  463. }
  464. lastHoveredFeature = feature;
  465. }
  466. } else {
  467. // 没有特征时,清空所有悬浮信息
  468. if (!this.hoveringInfo) this.hoverInfo = null;
  469. if (!this.hoveringFengji) this.hoverfengji = null;
  470. if (!this.hoveringTa) this.hoverta = null;
  471. this.currentFeatureData = null;
  472. lastHoveredFeature = null;
  473. }
  474. });
  475. // click 事件处理
  476. this.map.on("click", (evt) => {
  477. const features = this.map.getFeaturesAtPixel(evt.pixel, {
  478. hitTolerance: 1,
  479. });
  480. if (features && features.length > 0) {
  481. const feature = features.at(0);
  482. const data = feature.get("data");
  483. // 如果没有 data,直接返回,让事件继续冒泡
  484. if (!data) {
  485. return;
  486. }
  487. const val = data.val;
  488. if (val === "6") {
  489. this.handleFeatureClick(data);
  490. } else {
  491. this.$emit("feature-click", data);
  492. }
  493. } else if (
  494. this.currentFeatureData &&
  495. this.currentFeatureData.val === "6"
  496. ) {
  497. this.handleFeatureClick(this.currentFeatureData);
  498. }
  499. });
  500. },
  501. handleFeatureClick(featureData) {
  502. let dateArr = {
  503. batchCode: this.$route.query.batchCode,
  504. engineCode: featureData.engineCode,
  505. };
  506. queryErrDescByEngine(dateArr).then((res) => {
  507. this.dialogVisible = true;
  508. this.tableData = res.data;
  509. });
  510. },
  511. getwind() {
  512. // if (this.hoverInfo && this.hoverInfo.batchCode) {
  513. const param = {
  514. batchcode: this.hoverInfo.batchCode,
  515. fieldCode: this.hoverInfo.codeNumber,
  516. };
  517. queryFloatingWindowInfo(param)
  518. .then((res) => {
  519. this.newWind = res.data;
  520. })
  521. .catch((err) => {
  522. console.error("获取风信息失败", err);
  523. });
  524. // } else {
  525. // console.warn("hoverInfo 或 batchCode 未定义");
  526. // }
  527. },
  528. moveAndZoom(data = { point: [120.2, 30.35], zoom: 15 }) {
  529. this.map.getView().animate({
  530. center: fromLonLat(data.point, "EPSG:4326"),
  531. zoom: data.zoom,
  532. duration: 2000,
  533. });
  534. },
  535. },
  536. };
  537. </script>
  538. <style scoped lang="scss">
  539. .map-container {
  540. position: relative;
  541. width: 100%;
  542. height: 100%;
  543. overflow: hidden;
  544. }
  545. .map {
  546. width: 100%;
  547. height: 100%;
  548. }
  549. .hover-info {
  550. position: absolute;
  551. color: white;
  552. padding: 0;
  553. z-index: 100;
  554. h3 {
  555. background-color: var(--header-bg);
  556. width: 240px;
  557. padding: 5px 10px;
  558. font-size: 16px;
  559. }
  560. div {
  561. background-color: var(--content-bg);
  562. width: 240px;
  563. font-size: 12px;
  564. p {
  565. padding: 5px 10px;
  566. display: flex;
  567. justify-content: space-between;
  568. }
  569. }
  570. }
  571. .hover-fengji {
  572. position: absolute;
  573. color: white;
  574. padding: 0;
  575. z-index: 100;
  576. h3 {
  577. background-color: var(--header-bg);
  578. width: 240px;
  579. padding: 5px 10px;
  580. font-size: 16px;
  581. }
  582. div {
  583. background-color: var(--content-bg);
  584. width: 240px;
  585. font-size: 12px;
  586. p {
  587. padding: 5px 10px;
  588. display: flex;
  589. justify-content: space-between;
  590. }
  591. }
  592. }
  593. .hover-ta {
  594. position: absolute;
  595. color: white;
  596. padding: 0;
  597. z-index: 100;
  598. h3 {
  599. background-color: #d90019b2;
  600. width: 240px;
  601. padding: 5px 10px;
  602. font-size: 16px;
  603. }
  604. div {
  605. background-color: #d9001994;
  606. width: 240px;
  607. font-size: 12px;
  608. p {
  609. padding: 5px 10px;
  610. display: flex;
  611. justify-content: space-between;
  612. }
  613. }
  614. }
  615. </style>