index.vue 16 KB

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