index.vue 17 KB

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