index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  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://10.96.137.5:9080/tiles/{z}/{x}/{y}.png", //大~#@唐
  251. // url: "http://192.168.0.1/tiles/{z}/{x}/{y}.png", //华电
  252. // url: "http://192.168.50.235/tiles/{z}/{x}/{y}.png", //中广核
  253. url: process.env.VUE_APP_MAPVIEW,
  254. }),
  255. }),
  256. new VectorLayer({
  257. id: "marker",
  258. source: new Vector(),
  259. }),
  260. ],
  261. controls: defaultControls().extend([new ZoomSlider()]),
  262. });
  263. // -------------【添加山西省真实边界线】------------- 定位全国的时候注释这个代码
  264. // if (this.$route.path === "/home/cockpitManage") {
  265. // // 通过导入的 JSON 文件加载边界数据(GeoJSON 格式),并解析成矢量要素
  266. // const shanxiSource = new Vector({
  267. // features: new GeoJSON().readFeatures(shanxiBoundary, {
  268. // featureProjection: "EPSG:4326",
  269. // }),
  270. // });
  271. // // 创建矢量图层,仅显示边界线(填充颜色设为透明)
  272. // const shanxiLayer = new VectorLayer({
  273. // source: shanxiSource,
  274. // style: new Style({
  275. // stroke: new Stroke({
  276. // color: "rgba(59, 130, 246, 0.5)",
  277. // width: 3,
  278. // }),
  279. // fill: new Fill({
  280. // color: "rgba(59, 130, 246, 0.05)", // 透明填充
  281. // }),
  282. // }),
  283. // });
  284. // this.map.addLayer(shanxiLayer);
  285. // const markerLayer = this.map
  286. // .getLayers()
  287. // .getArray()
  288. // .find((layer) => {
  289. // return layer.get("id") === "marker";
  290. // });
  291. // if (markerLayer) {
  292. // markerLayer.setZIndex(10);
  293. // }
  294. // // 同时设置高亮图层 zIndex 较低
  295. // shanxiLayer.setZIndex(1);
  296. // // -------------【结束】-------------
  297. // const targetExtent = [106.8, 34.3, 118.6, 41.2];
  298. // this.map.getView().fit(targetExtent, { duration: 2000 });
  299. // }
  300. // 这个放在外面
  301. this.initEvent();
  302. },
  303. methods: {
  304. onHoverLeave() {
  305. this.hovering = false;
  306. clearTimeout(this.hideTimer);
  307. this.hideTimer = setTimeout(() => {
  308. if (!this.hovering) {
  309. this.hoverInfo = null;
  310. }
  311. }, 100); // 这里延迟100ms防止意外移出
  312. },
  313. onFengjiLeave() {
  314. this.hoveringFengji = false;
  315. clearTimeout(this.fengjiTimer);
  316. this.fengjiTimer = setTimeout(() => {
  317. if (!this.hoveringFengji) {
  318. this.hoverfengji = null;
  319. }
  320. }, 100); // 你可以根据实际需要调大一点时间
  321. },
  322. onTaLeave() {
  323. this.hoveringTa = false;
  324. clearTimeout(this.taTimer);
  325. this.taTimer = setTimeout(() => {
  326. if (!this.hoveringTa) {
  327. this.hoverta = null;
  328. }
  329. }, 100);
  330. },
  331. addMarker(data = { point: [120.2, 30.35], val: "1" }) {
  332. const layer = this.map
  333. .getLayers()
  334. .getArray()
  335. .find((element) => {
  336. return element.get("id") === "marker";
  337. });
  338. const source = layer.getSource();
  339. const iconSrc = this.getIconForValue(data.val);
  340. const scale = data.val === "4" ? [0.5, 0.5] : [0.3, 0.3];
  341. const feature = new Feature({
  342. geometry: new Point(fromLonLat(data.point, "EPSG:4326")),
  343. name: "marker",
  344. data,
  345. });
  346. feature.setStyle(
  347. new Style({
  348. image: new Icon({
  349. src: iconSrc,
  350. scale: scale,
  351. anchor: [0.5, 1],
  352. opacity: 1,
  353. }),
  354. })
  355. );
  356. source.addFeature(feature);
  357. },
  358. clearMarkers() {
  359. const layer = this.map
  360. .getLayers()
  361. .getArray()
  362. .find((element) => {
  363. return element.get("id") === "marker";
  364. });
  365. if (layer) {
  366. const source = layer.getSource();
  367. source.clear();
  368. }
  369. },
  370. getIconForValue(val) {
  371. switch (val) {
  372. case "-1":
  373. case -1:
  374. return icon01;
  375. case "10":
  376. case 10:
  377. return icon01;
  378. case 0:
  379. case "0":
  380. return icon02;
  381. case 20:
  382. case "20":
  383. return icon02;
  384. case 1:
  385. case "1":
  386. return icon03;
  387. case 30:
  388. case "30":
  389. return icon03;
  390. case "4":
  391. return icon04;
  392. case "5":
  393. return icon05;
  394. case "6":
  395. return icon06;
  396. default:
  397. return defaultIcon;
  398. }
  399. },
  400. initEvent() {
  401. let lastHoveredFeature = null;
  402. const calculateHoverTop = (mouseY, hoverHeight) => {
  403. const componentHeight = this.$el.clientHeight;
  404. const offset = 10;
  405. if (mouseY + hoverHeight + offset > componentHeight) {
  406. return mouseY - hoverHeight - offset;
  407. }
  408. return mouseY - hoverHeight / 2;
  409. };
  410. const calculateHoverLeft = (mouseX, hoverWidth) => {
  411. const componentWidth = this.$el.clientWidth;
  412. const offset = 10;
  413. if (mouseX + hoverWidth + offset > componentWidth) {
  414. return mouseX - hoverWidth - offset;
  415. }
  416. if (mouseX - hoverWidth - offset < 0) {
  417. return offset;
  418. }
  419. return mouseX + offset;
  420. };
  421. this.map.on("pointermove", (evt) => {
  422. const features = this.map.getFeaturesAtPixel(evt.pixel, {
  423. hitTolerance: 1,
  424. });
  425. if (features && features.length > 0) {
  426. const feature = features.at(0);
  427. const data = feature.get("data");
  428. // 如果没有 data 属性,则不处理,但允许事件冒泡
  429. if (!data) {
  430. return;
  431. }
  432. const val = data.val;
  433. // 如果当前悬停的 feature 与上一次不同,则处理
  434. if (lastHoveredFeature !== feature) {
  435. const hoverHeight = 250;
  436. const hoverWidth = 200;
  437. const topPosition = calculateHoverTop(evt.pixel[1], hoverHeight);
  438. const leftPosition = calculateHoverLeft(evt.pixel[0], hoverWidth);
  439. if (val == "1" || val == "30" || val == "-1" || val == "10") {
  440. this.hoverInfo = data;
  441. this.hoverStyle.left = `${leftPosition}px`;
  442. this.hoverStyle.top = `${topPosition}px`;
  443. this.getwind();
  444. } else if (val == "4") {
  445. this.hoverfengji = data;
  446. this.hoverfengjiStyle.left = `${leftPosition}px`;
  447. this.hoverfengjiStyle.top = `${topPosition}px`;
  448. this.currentFeatureData = data;
  449. } else if (val == "5") {
  450. this.hoverta = data;
  451. this.hovertaStyle.left = `${leftPosition}px`;
  452. this.hovertaStyle.top = `${topPosition}px`;
  453. this.currentFeatureData = data;
  454. } else if (val == "6") {
  455. this.currentFeatureData = data;
  456. } else {
  457. this.hoverInfo = false;
  458. this.hoverfengji = false;
  459. this.hoverta = false;
  460. this.currentFeatureData = false;
  461. }
  462. lastHoveredFeature = feature;
  463. }
  464. } else {
  465. // 没有特征时,清空所有悬浮信息
  466. if (!this.hoveringInfo) this.hoverInfo = null;
  467. if (!this.hoveringFengji) this.hoverfengji = null;
  468. if (!this.hoveringTa) this.hoverta = null;
  469. this.currentFeatureData = null;
  470. lastHoveredFeature = null;
  471. }
  472. });
  473. // click 事件处理
  474. this.map.on("click", (evt) => {
  475. const features = this.map.getFeaturesAtPixel(evt.pixel, {
  476. hitTolerance: 1,
  477. });
  478. if (features && features.length > 0) {
  479. const feature = features.at(0);
  480. const data = feature.get("data");
  481. // 如果没有 data,直接返回,让事件继续冒泡
  482. if (!data) {
  483. return;
  484. }
  485. const val = data.val;
  486. if (val === "6") {
  487. this.handleFeatureClick(data);
  488. } else {
  489. this.$emit("feature-click", data);
  490. }
  491. } else if (
  492. this.currentFeatureData &&
  493. this.currentFeatureData.val === "6"
  494. ) {
  495. this.handleFeatureClick(this.currentFeatureData);
  496. }
  497. });
  498. },
  499. handleFeatureClick(featureData) {
  500. let dateArr = {
  501. batchCode: this.$route.query.batchCode,
  502. engineCode: featureData.engineCode,
  503. };
  504. queryErrDescByEngine(dateArr).then((res) => {
  505. this.dialogVisible = true;
  506. this.tableData = res.data;
  507. });
  508. },
  509. getwind() {
  510. // if (this.hoverInfo && this.hoverInfo.batchCode) {
  511. const param = {
  512. batchcode: this.hoverInfo.batchCode,
  513. fieldCode: this.hoverInfo.codeNumber,
  514. };
  515. queryFloatingWindowInfo(param)
  516. .then((res) => {
  517. this.newWind = res.data;
  518. })
  519. .catch((err) => {
  520. console.error("获取风信息失败", err);
  521. });
  522. // } else {
  523. // console.warn("hoverInfo 或 batchCode 未定义");
  524. // }
  525. },
  526. moveAndZoom(data = { point: [120.2, 30.35], zoom: 15 }) {
  527. this.map.getView().animate({
  528. center: fromLonLat(data.point, "EPSG:4326"),
  529. zoom: data.zoom,
  530. duration: 2000,
  531. });
  532. },
  533. },
  534. };
  535. </script>
  536. <style scoped lang="scss">
  537. .map-container {
  538. position: relative;
  539. width: 100%;
  540. height: 100%;
  541. overflow: hidden;
  542. }
  543. .map {
  544. width: 100%;
  545. height: 100%;
  546. }
  547. .hover-info {
  548. position: absolute;
  549. color: white;
  550. padding: 0;
  551. z-index: 100;
  552. h3 {
  553. background-color: var(--header-bg);
  554. width: 240px;
  555. padding: 5px 10px;
  556. font-size: 16px;
  557. }
  558. div {
  559. background-color: var(--content-bg);
  560. width: 240px;
  561. font-size: 12px;
  562. p {
  563. padding: 5px 10px;
  564. display: flex;
  565. justify-content: space-between;
  566. }
  567. }
  568. }
  569. .hover-fengji {
  570. position: absolute;
  571. color: white;
  572. padding: 0;
  573. z-index: 100;
  574. h3 {
  575. background-color: var(--header-bg);
  576. width: 240px;
  577. padding: 5px 10px;
  578. font-size: 16px;
  579. }
  580. div {
  581. background-color: var(--content-bg);
  582. width: 240px;
  583. font-size: 12px;
  584. p {
  585. padding: 5px 10px;
  586. display: flex;
  587. justify-content: space-between;
  588. }
  589. }
  590. }
  591. .hover-ta {
  592. position: absolute;
  593. color: white;
  594. padding: 0;
  595. z-index: 100;
  596. h3 {
  597. background-color: #d90019b2;
  598. width: 240px;
  599. padding: 5px 10px;
  600. font-size: 16px;
  601. }
  602. div {
  603. background-color: #d9001994;
  604. width: 240px;
  605. font-size: 12px;
  606. p {
  607. padding: 5px 10px;
  608. display: flex;
  609. justify-content: space-between;
  610. }
  611. }
  612. }
  613. </style>