map.vue 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. <template>
  2. <div class="map-index">
  3. <!-- 苏州河 -->
  4. <div ref="scrollContainer" v-if="route.params.id=='28'" style="border: 2px solid #409EFF;background-color: rgba(127, 161, 215, 0.5);border-radius: 50px 50px 50px 50px;
  5. align-items: center;display: flex;position: absolute;z-index: 100;bottom: 1%;left: 1%;width:50vw;height: 10vh;overflow-x:auto;overflow-y: hidden;">
  6. <svg-icon icon-class="start1" v-if="!isStart" @click="reStart"
  7. style="margin-left: 10%;width: 30px;height:30px;cursor: pointer;"/>
  8. <svg-icon icon-class="pause1" v-if="isStart" @click="pause"
  9. style="margin-left: 10%;width: 30px;height:30px;cursor: pointer;"/>
  10. <el-slider @change="pauseIn" :format-tooltip="formatTooltip" v-model="activities1" :max="dateLength"
  11. :marks="marks" style="width: 80%;margin-left: 1%;"/>
  12. </div>
  13. <div v-if="route.params.id=='28'"
  14. style="background-color: white;position: absolute;z-index: 100;top: 1%;left: 1%;width: 20vw;height: 45vh;overflow:auto;">
  15. <div class="biz-data-card-header">方案列表</div>
  16. <el-table
  17. :data="tableDataFangan"
  18. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 40vh;"
  19. :cell-style="{ padding:'5px' }"
  20. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  21. :row-style="{ fontSize: '17px',textAlign:'center'}"
  22. border>
  23. <el-table-column type="index" label="序号" width="80">
  24. <template #default="{ $index }">
  25. <div style="text-align: center;">
  26. {{ $index + 1 }}
  27. </div>
  28. </template>
  29. </el-table-column>
  30. <el-table-column prop="name" label="名称">
  31. <template #default="scope">
  32. <span style="cursor: pointer;color:#409EFF" @click="getZ(scope.row)">{{ scope.row.name }}</span>
  33. </template>
  34. </el-table-column>
  35. </el-table>
  36. </div>
  37. <div v-if="route.params.id=='28'"
  38. style="background-color: white;position: absolute;z-index: 100;top: 1%;right: 1%;width: 30vw;height: 85vh;overflow:auto;">
  39. <div class="biz-data-card-header">水位信息</div>
  40. <el-table
  41. :data="tableData"
  42. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 80vh;"
  43. :cell-style="{ padding:'5px' }"
  44. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  45. :row-style="{ fontSize: '17px',textAlign:'center'}"
  46. border>
  47. <el-table-column type="index" label="序号" width="80">
  48. <template #default="{ $index }">
  49. <div style="text-align: center;">
  50. {{ $index + 1 }}
  51. </div>
  52. </template>
  53. </el-table-column>
  54. <el-table-column prop="name" label="站名" width="200">
  55. </el-table-column>
  56. <el-table-column prop="z" label="水位" width=""/>
  57. <el-table-column prop="time" label="时间" width="210"/>
  58. </el-table>
  59. </div>
  60. <div id="tooltip" ref="tooltipRef" class="tooltip"></div>
  61. <!-- 温带风暴潮 -->
  62. <div ref="scrollContainer" v-if="route.params.id=='20'" style="border: 2px solid #409EFF;background-color: rgba(127, 161, 215, 0.5);border-radius: 50px 50px 50px 50px;
  63. align-items: center;display: flex;position: absolute;z-index: 100;bottom: 1%;left: 1%;width:50vw;height: 10vh;overflow-x:auto;overflow-y: hidden;">
  64. <svg-icon icon-class="start1" v-if="!isStart" @click="reStartWen"
  65. style="margin-left: 10%;width: 30px;height:30px;cursor: pointer;"/>
  66. <svg-icon icon-class="pause1" v-if="isStart" @click="pauseInWen"
  67. style="margin-left: 10%;width: 30px;height:30px;cursor: pointer;"/>
  68. <el-slider @change="pauseIn" :format-tooltip="formatTooltip" v-model="activities1" :max="dateLength"
  69. :marks="marks" style="width: 80%;margin-left: 1%;"/>
  70. </div>
  71. <div v-if="route.params.id=='20'"
  72. style="background-color: white;position: absolute;z-index: 100;top: 1%;left: 1%;width: 20vw;height: 45vh;overflow:auto;">
  73. <div class="biz-data-card-header">方案列表</div>
  74. <el-table
  75. :data="tableDataFangan"
  76. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 40vh;"
  77. :cell-style="{ padding:'5px' }"
  78. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  79. :row-style="{ fontSize: '17px',textAlign:'center'}"
  80. border>
  81. <el-table-column type="index" label="序号" width="80">
  82. <template #default="{ $index }">
  83. <div style="text-align: center;">
  84. {{ $index + 1 }}
  85. </div>
  86. </template>
  87. </el-table-column>
  88. <el-table-column prop="name" label="名称">
  89. <template #default="scope">
  90. <span style="cursor: pointer;color:#409EFF" @click="getZ(scope.row)">{{ scope.row.name }}</span>
  91. </template>
  92. </el-table-column>
  93. </el-table>
  94. </div>
  95. <div v-if="route.params.id=='20'"
  96. style="background-color: white;position: absolute;z-index: 100;top: 1%;right: 1%;width: 30vw;height: 85vh;overflow:auto;">
  97. <div class="biz-data-card-header">风暴潮信息</div>
  98. <el-table
  99. :data="tableData"
  100. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 80vh;"
  101. :cell-style="{ padding:'5px' }"
  102. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  103. :row-style="{ fontSize: '17px',textAlign:'center'}"
  104. border>
  105. <el-table-column type="index" label="序号" width="80">
  106. <template #default="{ $index }">
  107. <div style="text-align: center;">
  108. {{ $index + 1 }}
  109. </div>
  110. </template>
  111. </el-table-column>
  112. <el-table-column prop="stnm" label="站名" width="150">
  113. </el-table-column>
  114. <el-table-column prop="gfsZ" label="水位" width=""/>
  115. <el-table-column prop="gfsAddz" label="增水" width=""/>
  116. <el-table-column prop="gfsAddz" label="风速" width=""/>
  117. <el-table-column prop="gfsWnddir" label="风向" width=""/>
  118. </el-table>
  119. </div>
  120. <!-- 排水 -->
  121. <div v-if="route.params.id=='30'"
  122. style="background-color: white;position: absolute;z-index: 100;top: 1%;left: 1%;width: 20vw;height: 45vh;overflow:auto;">
  123. <div class="biz-data-card-header">方案列表</div>
  124. <el-table
  125. :data="tableDataShanghaifeng"
  126. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 40vh;"
  127. :cell-style="{ padding:'5px' }"
  128. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  129. :row-style="{ fontSize: '17px',textAlign:'center'}"
  130. border>
  131. <el-table-column type="index" label="序号" width="80">
  132. <template #default="{ $index }">
  133. <div style="text-align: center;">
  134. {{ $index + 1 }}
  135. </div>
  136. </template>
  137. </el-table-column>
  138. <el-table-column prop="planName" label="名称">
  139. <template #default="scope">
  140. <span style="cursor: pointer;color:#409EFF" @click="getZ(scope.row)">{{ scope.row.planName }}</span>
  141. </template>
  142. </el-table-column>
  143. </el-table>
  144. </div>
  145. <!-- 测试 -->
  146. <div v-if="route.params.id=='42'"
  147. style="background-color: white;position: absolute;z-index: 100;top: 1%;left: 1%;width: 30vw;height: 45vh;overflow:auto;">
  148. <div class="biz-data-card-header">方案列表</div>
  149. <el-table
  150. :data="tableDataShanghaifeng"
  151. style="width: 98%;margin-left: 1%;margin-top: 1%;height: 40vh;"
  152. :cell-style="{ padding:'5px' }"
  153. :header-cell-style="{fontSize: '14px',height: heightAll*0.01+'px',}"
  154. :row-style="{ fontSize: '17px',textAlign:'center'}"
  155. border>
  156. <el-table-column type="index" label="序号" width="80">
  157. <template #default="{ $index }">
  158. <div style="text-align: center;">
  159. {{ $index + 1 }}
  160. </div>
  161. </template>
  162. </el-table-column>
  163. <el-table-column prop="planName" label="名称" width="150">
  164. <template #default="scope">
  165. <span style="cursor: pointer;color:#409EFF" @click="getGeo(scope.row)">{{ scope.row.dataName }}</span>
  166. </template>
  167. </el-table-column>
  168. <el-table-column prop="createTime" label="产生时间" width="200">
  169. </el-table-column>
  170. <el-table-column prop="timeLength" label="存放时长(天)">
  171. </el-table-column>
  172. </el-table>
  173. </div>
  174. <el-dialog @close="clearFromLev" :title="titleFengbao" v-model="dialogVisibleFengbao" width="50%" destroy-on-close>
  175. <div>
  176. <div style="font-size: 20px;margin-left: 2%;">水位</div>
  177. <div id="shuiwei" style="width: 100%;height: 18vh;margin-top: 0%;"></div>
  178. </div>
  179. <div>
  180. <div style="font-size: 20px;margin-left: 2%;margin-top:1% ;">增水</div>
  181. <div id="zengshui" style="width: 100%;height: 18vh;margin-top: 1%;"></div>
  182. </div>
  183. <div>
  184. <div style="font-size: 20px;margin-left: 2%;margin-top:1%">风速</div>
  185. <div id="fengsu" style="width: 100%;height: 18vh;margin-top: 1%;"></div>
  186. </div>
  187. <div>
  188. <div style="font-size: 20px;margin-left: 2%;margin-top:1%">风向</div>
  189. <div id="fengxiang" style="width: 100%;height:18vh;margin-top: 1%;"></div>
  190. </div>
  191. </el-dialog>
  192. <el-dialog @close="clearFromLev" title="" v-model="dialogVisibleSuzhou" width="30%" destroy-on-close>
  193. <el-descriptions
  194. class="margin-top"
  195. :title="titleSuzhou"
  196. :column="1"
  197. :size="size"
  198. border
  199. >
  200. <el-descriptions-item>
  201. <template #label>
  202. <div class="cell-item">
  203. 站码
  204. </div>
  205. </template>
  206. {{ suzhou.stationId }}
  207. </el-descriptions-item>
  208. <el-descriptions-item>
  209. <template #label>
  210. <div class="cell-item">
  211. 水位
  212. </div>
  213. </template>
  214. {{ suzhou.waterLevel }}
  215. </el-descriptions-item>
  216. <el-descriptions-item>
  217. <template #label>
  218. <div class="cell-item">
  219. 时间
  220. </div>
  221. </template>
  222. {{ suzhou.tm }}
  223. </el-descriptions-item>
  224. </el-descriptions>
  225. </el-dialog>
  226. <div id="mapChart"></div>
  227. </div>
  228. </template>
  229. <script setup>
  230. import 'ol/css';
  231. import {defaults as defaultControls} from 'ol/control';
  232. import {
  233. getForecastlist,
  234. getStromdatade,
  235. getStromdataList,
  236. getStromlist,
  237. getMdAppFlowDataList,
  238. getAppData
  239. } from "@/api/standardization/bizDataShowConfig.js";
  240. import Map from 'ol/Map';
  241. import View from 'ol/View';
  242. import TileLayer from "ol/layer/Tile";
  243. import VectorLayer from "ol/layer/Vector";
  244. import Overlay from 'ol/Overlay';
  245. import {XYZ} from 'ol/source';
  246. import Point from 'ol/geom/Point';
  247. import * as echarts from 'echarts';
  248. import {createDynamicStyle} from "@/views/map/utils/styleParser.js";
  249. import {loadSource} from "../hooks/dataSourceManager.js";
  250. import bus from "@/utils/bus.js";
  251. import {getPopupContentByTemplate, loadPopupChartByTemplate} from "@/views/map/hooks/popupContent.js";
  252. import {isArray} from "@/utils/validate.js";
  253. import {getCenter} from "ol/extent.js";
  254. import jsonDatak5 from "./kaixuan5.json";
  255. import 'ol/ol.css';
  256. import VectorSource from 'ol/source/Vector';
  257. import Feature from 'ol/Feature';
  258. import {Fill, RegularShape, Stroke, Style, Text} from 'ol/style';
  259. import GeoJSON from 'ol/format/GeoJSON';
  260. import suzhouJson from "./suzhou.json";
  261. import {useRoute} from "vue-router";
  262. import suzhouPoint from "./suzhouPoints.json";
  263. import {ref} from 'vue';
  264. import fangan from "./fangan.json";
  265. import test1 from "./test1.json";
  266. import test2 from "./test2.json";
  267. import test3 from "./test3.json";
  268. const dialogVisibleFengbao = ref(false)
  269. const titleFengbao = ref('')
  270. const tableDataFangan = ref([])
  271. const fanganId = ref(null)
  272. const scrollContainer = ref(null);
  273. const scrollAmount = 0;
  274. const widthAll = ref(window.innerWidth)
  275. const dialogVisibleSuzhou = ref(false)
  276. const route = useRoute();
  277. const suzhou = ref({})
  278. const props = defineProps({
  279. config: Object,
  280. });
  281. const mapChart = ref(null);
  282. // 存储弹窗的引用
  283. const popupOverlays = ref([]);
  284. const activities = ref([])
  285. const activities1 = ref()
  286. const dateLength = ref(0)
  287. const marks = ref({})
  288. const isStart = ref(true)
  289. const tooltipRef = ref(null);
  290. let currentFeature = null
  291. let overlay = null
  292. const tableDataShanghaifeng = ref([])
  293. const geoJSONLayer = ref(null)
  294. onMounted(async () => {
  295. console.log(route.params.id)
  296. initMap();
  297. if (route.params.id === '30') {
  298. getForecastlist({appId: route.params.id}).then(res => {
  299. tableDataShanghaifeng.value = res.rows
  300. })
  301. polygonLayer.value = createPolygonLayer(jsonDatak5)
  302. mapChart.value.addLayer(polygonLayer.value);
  303. overlay = new Overlay({
  304. element: tooltipRef.value,
  305. positioning: 'bottom-center',
  306. offset: [0, -10],
  307. stopEvent: false,
  308. });
  309. mapChart.value.addOverlay(overlay);
  310. mapChart.value.getView().setZoom(18)
  311. mapChart.value.getView().setCenter([121.41335460526926, 31.205820744558153]);
  312. mapChart.value.on('pointermove', (e) => {
  313. if (e.dragging) {
  314. return; // 如果正在拖拽地图,则不处理[2,7](@ref)
  315. }
  316. const pixel = mapChart.value.getEventPixel(e.originalEvent);
  317. const feature = mapChart.value.forEachFeatureAtPixel(pixel, (feature) => feature);
  318. if (feature) {
  319. // 鼠标在要素上[7](@ref)
  320. if (feature !== currentFeature) {
  321. currentFeature = feature;
  322. console.log(feature.values_)
  323. // 获取要素属性信息
  324. const featureCode = feature.values_.element_no;
  325. const featureDep = feature.values_.DEPTH2D
  326. // 更新工具提示内容[2](@ref)
  327. tooltipRef.value.innerHTML = `
  328. <div class="tooltip-header">编号:${featureCode}</div>
  329. <div class="tooltip-content">水深: ${featureDep}</div>
  330. `;
  331. // 设置工具提示位置并显示[7](@ref)
  332. overlay.setPosition(e.coordinate);
  333. tooltipRef.value.style.display = 'block';
  334. // 更改鼠标指针样式为手型[3,5](@ref)
  335. mapChart.value.getTargetElement().style.cursor = 'pointer';
  336. }
  337. } else {
  338. // 鼠标不在要素上
  339. tooltipRef.value.style.display = 'none';
  340. currentFeature = null;
  341. mapChart.value.getTargetElement().style.cursor = ''; // 恢复默认指针[3](@ref)
  342. }
  343. });
  344. }
  345. if (route.params.id === '42') {
  346. getMdAppFlowDataList({appId: route.params.id}).then(res => {
  347. tableDataShanghaifeng.value = res.data
  348. getGeo(tableDataShanghaifeng.value[0])
  349. })
  350. }
  351. if (route.params.id === '28') {
  352. tableDataFangan.value = fangan.data.records
  353. const keys = Object.keys(suzhouJson.data.outPutQUZ)
  354. dateLength.value = keys.length - 1
  355. keys.forEach(item => {
  356. var par = {
  357. content: '',
  358. timestamp: item,
  359. size: 'large',
  360. type: 'primary',
  361. }
  362. activities.value.push(par)
  363. })
  364. var parTime = uniformSample(activities.value)
  365. console.log(activities.value)
  366. var a = Math.round((dateLength.value - 1) * 1 / 3)
  367. var b = Math.round((dateLength.value - 1) * 2 / 3)
  368. var c = dateLength.value
  369. marks.value = {
  370. 0: {
  371. style: {
  372. color: '#1989FA',
  373. },
  374. label: parTime[0].timestamp.slice(5),
  375. },
  376. }
  377. marks.value[a] = {
  378. style: {
  379. color: '#1989FA',
  380. },
  381. label: parTime[1].timestamp.slice(5),
  382. }
  383. marks.value[b] = {
  384. style: {
  385. color: '#1989FA',
  386. },
  387. label: parTime[2].timestamp.slice(5),
  388. }
  389. marks.value[c] = {
  390. style: {
  391. color: '#1989FA',
  392. },
  393. label: parTime[3].timestamp.slice(5),
  394. }
  395. console.log(activities)
  396. suzhouJson.data.inputParam = JSON.parse(suzhouJson.data.inputParam)
  397. console.log(suzhouJson.data)
  398. timerId = setInterval(changeMap, 1000)
  399. console.log(suzhouPoint)
  400. pointLayer.value = createPointlayer(suzhouPoint)
  401. mapChart.value.addLayer(pointLayer.value);
  402. mapChart.value.on('click', (event) => {
  403. const feature = mapChart.value.forEachFeatureAtPixel(
  404. event.pixel,
  405. (feature) => feature
  406. );
  407. if (feature) {
  408. pause()
  409. const properties = feature.getProperties();
  410. dialogVisibleSuzhou.value = true
  411. titleSuzhou.value = properties.name
  412. console.log('要素属性:', properties);
  413. suzhou.value = properties
  414. suzhou.value.tm = activities.value[count.value - 1].timestamp
  415. const title = feature.get('title');
  416. const id = feature.get('id');
  417. console.log('标题:', title, 'ID:', id);
  418. // 获取几何信息
  419. const geometry = feature.getGeometry();
  420. const coordinates = geometry.getCoordinates();
  421. console.log('几何坐标:', coordinates);
  422. } else {
  423. console.log('未点击到任何要素');
  424. }
  425. })
  426. if (props.config) {
  427. // toCenter(props.config.center, props.config.zoom);
  428. await loadLayers(props.config.layers);
  429. }
  430. }
  431. if (route.params.id === '20') {
  432. tableDataFangan.value = fangan.data.records
  433. wendai()
  434. }
  435. });
  436. const titleSuzhou = ref('')
  437. const count = ref(0)
  438. const tableData = ref()
  439. function getZ(row) {
  440. console.log(row.id)
  441. }
  442. function setGeoJSONLayer(json){
  443. var parData = []
  444. json.features.forEach(item => {
  445. var par = {
  446. type: 'Feature',
  447. geometry: item.geometry,
  448. properties: item.properties,
  449. }
  450. parData.push(par)
  451. });
  452. const polygonGeoJSON = {
  453. "type": "FeatureCollection",
  454. "features": parData
  455. };
  456. // 创建矢量数据源并加载GeoJSON多边形数据[3,4](@ref)
  457. const vectorSource = new VectorSource({
  458. features: new GeoJSON().readFeatures(polygonGeoJSON, {
  459. dataProjection: 'EPSG:4326',
  460. featureProjection: 'EPSG:4326'
  461. })
  462. });
  463. // 3. 创建矢量图层
  464. const styleFunction = function (feature) {
  465. const properties = feature.getProperties();
  466. if (properties.hvalue >= 50) {
  467. return new Style({
  468. fill: new Fill({
  469. color: 'rgba(248, 152, 152, 0.5)'
  470. }),
  471. stroke: new Stroke({
  472. color: '#ff0000',
  473. width: 2
  474. })
  475. });
  476. } else if (properties.hvalue < 50 && properties.hvalue >= 40) {
  477. return new Style({
  478. fill: new Fill({
  479. color: 'rgba(255, 0, 0, 0.5)'
  480. }),
  481. stroke: new Stroke({
  482. color: '#ff0000',
  483. width: 2
  484. })
  485. });
  486. } else if (properties.hvalue < 40 && properties.hvalue >= 30) {
  487. return new Style({
  488. fill: new Fill({
  489. color: 'rgba(51, 126, 204, 0.5)'
  490. }),
  491. stroke: new Stroke({
  492. color: '#ff0000',
  493. width: 2
  494. })
  495. });
  496. } else if (properties.hvalue < 30 && properties.hvalue >= 20) {
  497. return new Style({
  498. fill: new Fill({
  499. color: 'rgba(121, 187, 255, 0.5)'
  500. }),
  501. stroke: new Stroke({
  502. color: '#ff0000',
  503. width: 2
  504. })
  505. });
  506. } else if (properties.hvalue < 10 && properties.hvalue >= 1) {
  507. return new Style({
  508. fill: new Fill({
  509. color: 'rgba(149, 212, 117, 0.5)'
  510. }),
  511. stroke: new Stroke({
  512. color: '#ff0000',
  513. width: 2
  514. })
  515. });
  516. }
  517. // 默认样式
  518. return new Style({
  519. fill: new Fill({
  520. color: 'rgba(179, 225, 157, 0.5)' // 半透明蓝色填充
  521. }),
  522. stroke: new Stroke({
  523. color: '#0066ff', // 蓝色边框
  524. width: 2 // 边框宽度
  525. })
  526. });
  527. };
  528. const geoJSONLayer = new VectorLayer({
  529. source: vectorSource,
  530. style: styleFunction // 应用样式
  531. });
  532. return geoJSONLayer
  533. }
  534. function initChart(x, y, mdName, id, color, danwei) {
  535. var chartDom = document.getElementById(id);
  536. var myChart = echarts.init(chartDom);
  537. var option;
  538. option = {
  539. tooltip: {
  540. trigger: 'axis'
  541. },
  542. grid: {
  543. left: '5%',
  544. right: '5%',
  545. bottom: '15%',
  546. top: '18%',
  547. containLabel: true
  548. },
  549. xAxis: {
  550. splitLine: {show: false},
  551. // type: 'category',
  552. data: x
  553. },
  554. yAxis: {
  555. type: 'value',
  556. show: true,
  557. splitLine: {show: false},
  558. name: danwei,
  559. axisTick: {
  560. show: true // 确保显示刻度线
  561. },
  562. axisLine: {
  563. show: true, // 确保显示轴线
  564. lineStyle: {
  565. color: '#333', // 可以设置轴线的颜色,例如与文字颜色一致
  566. // width: 1 // 可以设置轴线宽度,可选
  567. }
  568. },
  569. },
  570. series: [
  571. {
  572. name: mdName,
  573. type: 'line',
  574. color: color,
  575. data: y,
  576. smooth: true
  577. },
  578. ]
  579. };
  580. option && myChart.setOption(option);
  581. }
  582. function formatTooltip(val) {
  583. if (activities.value && activities.value[val]) {
  584. return activities.value[val].timestamp.slice(5);
  585. }
  586. // 数据未就绪时返回默认提示
  587. return 'Loading...';
  588. }
  589. const tableJason = ref([])
  590. async function wendai() {
  591. await getStromlist().then(res => {
  592. var dataJson = res.data
  593. const vectorSource = new VectorSource();
  594. dataJson.forEach(station => {
  595. const feature = new Feature({
  596. geometry: new Point([station.lgtd, station.lttd]),
  597. stcd: station.stcd,
  598. stnm: station.stnm,
  599. });
  600. // 设置样式(根据水位值动态设置颜色)
  601. const style = new Style({
  602. image: new RegularShape({
  603. points: 3, // 三角形
  604. radius: 8, // 大小
  605. fill: new Fill({
  606. color: '#FF5722' // 红色填充
  607. }),
  608. stroke: new Stroke({
  609. color: '#fff', // 白色边框
  610. width: 2
  611. }),
  612. angle: Math.PI // 旋转180度,使三角形倒置[1,4](@ref)
  613. }),
  614. text: new Text({
  615. text: station.stnm,
  616. offsetY: 20,
  617. font: '12px Microsoft YaHei, sans-serif',
  618. fill: new Fill({
  619. color: '#333'
  620. }),
  621. stroke: new Stroke({
  622. color: '#fff',
  623. width: 1
  624. }),
  625. backgroundFill: new Fill({
  626. color: 'transparent'
  627. }),
  628. padding: [2, 4, 2, 4]
  629. })
  630. });
  631. feature.setStyle(style);
  632. vectorSource.addFeature(feature);
  633. });
  634. const vectorLayer = new VectorLayer({
  635. source: vectorSource,
  636. properties: {
  637. name: 'weidaiLayers',
  638. type: 'vector'
  639. }
  640. });
  641. mapChart.value.addLayer(vectorLayer)
  642. mapChart.value.on('click', (event) => {
  643. const feature = mapChart.value.forEachFeatureAtPixel(
  644. event.pixel,
  645. (feature) => feature
  646. );
  647. if (feature) {
  648. const properties = feature.getProperties();
  649. dialogVisibleFengbao.value = true
  650. titleFengbao.value = properties.stnm + '(站码:' + properties.stcd + ')'
  651. getStromdatade({stcd: properties.stcd}).then(res => {
  652. var x = []
  653. var ygfsZ = []
  654. var ygfsAddz = []
  655. var ygfsWndv = []
  656. var ygfsWnddir = []
  657. res.data.forEach(item => {
  658. var parTm = item.tm.slice(5)
  659. x.push(parTm.slice(0, 5) + '\n' + parTm.slice(-8, -3))
  660. ygfsZ.push(item.gfsZ)
  661. ygfsAddz.push(item.gfsAddz)
  662. ygfsWndv.push(item.gfsWndv)
  663. ygfsWnddir.push(item.gfsWnddir)
  664. })
  665. initChart(x, ygfsZ, properties.stnm, 'shuiwei', '#409EFF', '单位:m')
  666. initChart(x, ygfsAddz, properties.stnm, 'zengshui', '#67C23A', '单位:m')
  667. initChart(x, ygfsWndv, properties.stnm, 'fengsu', '#E6A23C', '单位:m/s')
  668. initChart(x, ygfsWnddir, properties.stnm, 'fengxiang', '#F56C6C', '单位:°')
  669. })
  670. console.log('要素属性:', properties);
  671. // 获取特定属性
  672. const title = feature.get('title');
  673. const id = feature.get('id');
  674. console.log('标题:', title, 'ID:', id);
  675. // 获取几何信息
  676. const geometry = feature.getGeometry();
  677. const coordinates = geometry.getCoordinates();
  678. console.log('几何坐标:', coordinates);
  679. } else {
  680. console.log('未点击到任何要素');
  681. }
  682. })
  683. })
  684. await getStromdataList().then(res => {
  685. tableJason.value = res.data
  686. const keys = Object.keys(res.data)
  687. dateLength.value = keys.length
  688. console.log(keys, dateLength.value)
  689. keys.forEach(item => {
  690. var par = {
  691. content: '',
  692. timestamp: item,
  693. size: 'large',
  694. type: 'primary',
  695. }
  696. activities.value.push(par)
  697. })
  698. var parTime = uniformSample(activities.value)
  699. console.log(activities.value)
  700. var a = Math.round((dateLength.value - 1) * 1 / 3)
  701. var b = Math.round((dateLength.value - 1) * 2 / 3)
  702. var c = dateLength.value - 1
  703. marks.value = {
  704. 0: {
  705. style: {
  706. color: '#1989FA',
  707. },
  708. label: parTime[0].timestamp.slice(5),
  709. },
  710. }
  711. marks.value[a] = {
  712. style: {
  713. color: '#1989FA',
  714. },
  715. label: parTime[1].timestamp.slice(5),
  716. }
  717. marks.value[b] = {
  718. style: {
  719. color: '#1989FA',
  720. },
  721. label: parTime[2].timestamp.slice(5),
  722. }
  723. marks.value[c] = {
  724. style: {
  725. color: '#1989FA',
  726. },
  727. label: parTime[3].timestamp.slice(5),
  728. }
  729. })
  730. dateLength.value = dateLength.value - 1
  731. timerId = setInterval(changeWen, 1000)
  732. }
  733. function getGeo(row){
  734. getAppData({dataId:row.dataId}).then(res=>{
  735. mapChart.value.removeLayer(geoJSONLayer.value)
  736. geoJSONLayer.value = setGeoJSONLayer(res.data)
  737. mapChart.value.addLayer(geoJSONLayer.value);
  738. })
  739. }
  740. function changeWen() {
  741. if (tableJason.value) {
  742. tableData.value = []
  743. var valuesArray = Object.values(tableJason.value)
  744. tableData.value = valuesArray[count.value]
  745. activities1.value = count.value
  746. count.value++
  747. if (count.value > dateLength.value) {
  748. count.value = 0
  749. }
  750. }
  751. }
  752. function pause() {
  753. clearInterval(timerId);
  754. timerId = null
  755. isStart.value = false
  756. }
  757. function reStart() {
  758. timerId = setInterval(changeMap, 1000)
  759. isStart.value = true
  760. }
  761. function reStartWen() {
  762. timerId = setInterval(changeWen, 1000)
  763. isStart.value = true
  764. }
  765. function uniformSample(arr) {
  766. const n = arr.length;
  767. if (n === 0) return [];
  768. if (n <= 4) {
  769. return arr.slice();
  770. }
  771. // 计算三个等分点的索引
  772. const index0 = 0; // 首元素
  773. const index1 = Math.round((n - 1) * 1 / 3); // 第一个等分点
  774. const index2 = Math.round((n - 1) * 2 / 3); // 第二个等分点
  775. const index3 = n - 1; // 尾元素
  776. return [
  777. arr[index0],
  778. arr[index1],
  779. arr[index2],
  780. arr[index3]
  781. ];
  782. }
  783. function pauseIn() {
  784. pause()
  785. count.value = activities1.value
  786. changeMap()
  787. }
  788. function pauseInWen() {
  789. pause()
  790. count.value = activities1.value
  791. changeWen()
  792. }
  793. function changeMap() {
  794. tableData.value = []
  795. const keys1 = Object.keys(suzhouJson.data.outPutQUZ)
  796. if (count.value >= keys1.length) {
  797. console.log(count.value, keys1.length)
  798. count.value = 0
  799. }
  800. activities1.value = count.value
  801. mapChart.value.removeLayer(pointLayer.value)
  802. const keys = Object.keys(suzhouJson.data.outPutQUZ)
  803. tableData.value = suzhouJson.data.outPutQUZ[keys[count.value]]
  804. for (var i = 0; i < suzhouPoint.length; i++) {
  805. for (var i1 = 0; i1 < suzhouJson.data.outPutQUZ[keys[count.value]].length; i1++) {
  806. if (suzhouPoint[i].STATIONNAME === suzhouJson.data.outPutQUZ[keys[count.value]][i1].name) {
  807. suzhouPoint[i].ZZ = suzhouJson.data.outPutQUZ[keys[count.value]][i1].z
  808. }
  809. }
  810. }
  811. pointLayer.value = createPointlayer(suzhouPoint)
  812. mapChart.value.addLayer(pointLayer.value);
  813. // if(count.value%10===0&&count.value!==0){
  814. // scrollContainer.value.scrollLeft += scrollAmount + widthAll.value;
  815. // }
  816. count.value++
  817. }
  818. function createPolygonLayer(jsonData) {
  819. var parData = []
  820. if (Array.isArray(jsonData)) {
  821. jsonData.forEach(item => {
  822. var par = {
  823. type: 'Feature',
  824. geometry: JSON.parse(item.geometry),
  825. properties: JSON.parse(item.properties),
  826. }
  827. parData.push(par)
  828. });
  829. const polygonGeoJSON = {
  830. "type": "FeatureCollection",
  831. "features": parData
  832. };
  833. // 创建矢量数据源并加载GeoJSON多边形数据[3,4](@ref)
  834. const vectorSource = new VectorSource({
  835. features: new GeoJSON().readFeatures(polygonGeoJSON, {
  836. dataProjection: 'EPSG:4326',
  837. featureProjection: 'EPSG:4326'
  838. })
  839. });
  840. // 创建多边形要素样式[6,7](@ref)
  841. const styleFunction = function (feature) {
  842. const properties = feature.getProperties();
  843. if (properties.DEPTH2D >= 1) {
  844. return new Style({
  845. fill: new Fill({
  846. color: 'rgba(255, 0, 0, 0.5)'
  847. }),
  848. stroke: new Stroke({
  849. color: '#ff0000',
  850. width: 3
  851. })
  852. });
  853. } else if (properties.DEPTH2D < 1 && properties.DEPTH2D >= 0.5) {
  854. return new Style({
  855. fill: new Fill({
  856. color: 'rgb(238, 190, 119)'
  857. }),
  858. stroke: new Stroke({
  859. color: '#ff0000',
  860. width: 3
  861. })
  862. });
  863. } else if (properties.DEPTH2D < 0.5 && properties.DEPTH2D >= 0.3) {
  864. return new Style({
  865. fill: new Fill({
  866. color: 'rgb(238, 190, 119)'
  867. }),
  868. stroke: new Stroke({
  869. color: '#ff0000',
  870. width: 3
  871. })
  872. });
  873. } else if (properties.DEPTH2D < 0.3 && properties.DEPTH2D >= 0.25) {
  874. return new Style({
  875. fill: new Fill({
  876. color: 'rgb(238, 190, 119)'
  877. }),
  878. stroke: new Stroke({
  879. color: '#ff0000',
  880. width: 3
  881. })
  882. });
  883. } else if (properties.DEPTH2D < 0.25 && properties.DEPTH2D >= 0.2) {
  884. return new Style({
  885. fill: new Fill({
  886. color: 'rgb(121, 187, 255)'
  887. }),
  888. stroke: new Stroke({
  889. color: '#ff0000',
  890. width: 3
  891. })
  892. });
  893. } else if (properties.DEPTH2D < 0.2 && properties.DEPTH2D >= 0.15) {
  894. return new Style({
  895. fill: new Fill({
  896. color: 'rgb(198, 226, 255)'
  897. }),
  898. stroke: new Stroke({
  899. color: '#ff0000',
  900. width: 3
  901. })
  902. });
  903. }
  904. // 默认样式
  905. return new Style({
  906. fill: new Fill({
  907. color: 'rgb(51, 126, 204)' // 半透明蓝色填充
  908. }),
  909. stroke: new Stroke({
  910. color: '#0066ff', // 蓝色边框
  911. width: 2 // 边框宽度
  912. })
  913. });
  914. };
  915. // 创建矢量图层[1,2](@ref)
  916. return new VectorLayer({
  917. source: vectorSource,
  918. style: styleFunction
  919. });
  920. }
  921. }
  922. function createStationStyle(station) {
  923. // 根据水位值设置颜色[7](@ref)
  924. let color = '#3399CC'; // 默认蓝色
  925. if (station.ZZ > 5.0) {
  926. color = '#FF6B6B'; // 高水位用红色
  927. } else if (station.ZZ < 4.5) {
  928. color = '#4ECDC4'; // 低水位用绿色
  929. }
  930. return new Style({
  931. image: new RegularShape({
  932. points: 3, // 三角形
  933. radius: 8, // 大小
  934. fill: new Fill({
  935. color: '#FF5722' // 红色填充
  936. }),
  937. stroke: new Stroke({
  938. color: '#fff', // 白色边框
  939. width: 2
  940. }),
  941. angle: Math.PI // 旋转180度,使三角形倒置[1,4](@ref)
  942. }),
  943. text: new Text({
  944. text: station.STATIONNAME + '\n水位: ' + station.ZZ + 'm',
  945. offsetY: 20,
  946. font: '12px Microsoft YaHei, sans-serif',
  947. fill: new Fill({
  948. color: '#333'
  949. }),
  950. stroke: new Stroke({
  951. color: '#fff',
  952. width: 1
  953. }),
  954. backgroundFill: new Fill({
  955. color: 'transparent'
  956. }),
  957. padding: [2, 4, 2, 4]
  958. })
  959. });
  960. }
  961. function createPointlayer(dataJson) {
  962. const vectorSource = new VectorSource();
  963. // 为每个水文站创建要素[2,3](@ref)
  964. dataJson.forEach(station => {
  965. // 使用经纬度坐标创建点要素[5](@ref)
  966. const feature = new Feature({
  967. geometry: new Point([station.XX2000, station.YY2000]),
  968. name: station.STATIONNAME,
  969. waterLevel: station.ZZ,
  970. area: station.QUYU,
  971. river: station.HELIU,
  972. type: station.TYPE,
  973. // tm:,
  974. stationId: station.STATIONID
  975. });
  976. // 设置样式(根据水位值动态设置颜色)
  977. const style = createStationStyle(station);
  978. feature.setStyle(style);
  979. vectorSource.addFeature(feature);
  980. });
  981. // 创建矢量图层[1,4](@ref)
  982. const vectorLayer = new VectorLayer({
  983. source: vectorSource,
  984. // 可以设置图层属性便于后续管理
  985. properties: {
  986. name: 'hydrologicalStations',
  987. type: 'vector'
  988. }
  989. });
  990. // 将图层添加到地图
  991. return vectorLayer;
  992. }
  993. // 创建多边形图层
  994. const pointLayer = ref(null)
  995. const polygonLayer = ref(null)
  996. const initMap = () => {
  997. let vecLayer = new TileLayer({
  998. source: new XYZ({
  999. url: "http://t0.tianditu.gov.cn/vec_w/wmts?" +
  1000. "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
  1001. "&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" +
  1002. "&tk=9bb941214f10fbf9a3eab43f45cb2b7e",
  1003. }),
  1004. });
  1005. let cvaLayer = new TileLayer({
  1006. source: new XYZ({
  1007. url: "http://t0.tianditu.gov.cn/cva_w/wmts?" +
  1008. "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
  1009. "&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" +
  1010. "&tk=9bb941214f10fbf9a3eab43f45cb2b7e",
  1011. }),
  1012. });
  1013. mapChart.value = new Map({
  1014. target: 'mapChart',
  1015. view: new View({
  1016. center: [121.472644, 31.231706],
  1017. zoom: 10,
  1018. minZoom: 3,
  1019. maxZoom: 16,
  1020. projection: 'EPSG:4326',
  1021. }),
  1022. layers: [vecLayer, cvaLayer],
  1023. // layers: [vecLayer],
  1024. controls: defaultControls({
  1025. zoom: false,//不显示放大放小按钮
  1026. rotate: false,//不显示指北针控件
  1027. attribution: false,//不显示右下角的地图信息控件
  1028. scaleLine: false,//不显示比例尺控件
  1029. })
  1030. });
  1031. mapChart.value.on('moveend', function (event) {
  1032. var center = mapChart.value.getView().getCenter(); // 获取中心点位置
  1033. var zoom = mapChart.value.getView().getZoom(); // 获取层级
  1034. console.log('Center:', center); // 转换为经纬度格式
  1035. console.log('Zoom:', zoom);
  1036. });
  1037. };
  1038. const addVectorLayer = async (config) => {
  1039. loadSource(config).then(source => {
  1040. // 创建矢量图层
  1041. const vectorLayer = new VectorLayer({
  1042. id: config.id,
  1043. zIndex: config.zIndex,
  1044. source: source,
  1045. style: config.style ? createDynamicStyle(config.style) : null,
  1046. visible: config.visible !== false // 默认可见
  1047. });
  1048. // 添加到地图
  1049. mapChart.value.addLayer(vectorLayer);
  1050. // 添加事件监听
  1051. if (config.events) {
  1052. setupLayerEvents(vectorLayer, config.events);
  1053. }
  1054. return vectorLayer;
  1055. })
  1056. };
  1057. // 设置图层事件监听
  1058. const setupLayerEvents = (layer, events) => {
  1059. // 点击事件处理
  1060. if (events.click) {
  1061. mapChart.value.on('click', (event) => {
  1062. const features = mapChart.value.getFeaturesAtPixel(event.pixel);
  1063. if (features && features.length > 0) {
  1064. // 处理点击事件,例如显示弹窗
  1065. handleFeatureClick(features[0], events.click);
  1066. }
  1067. });
  1068. }
  1069. // 悬停事件处理
  1070. if (events.hover) {
  1071. mapChart.value.on('pointermove', (event) => {
  1072. const features = mapChart.value.getFeaturesAtPixel(event.pixel);
  1073. if (features && features.length > 0) {
  1074. // 处理悬停事件,例如高亮显示
  1075. handleFeatureHover(features[0], events.hover);
  1076. }
  1077. });
  1078. }
  1079. };
  1080. // 处理要素点击事件
  1081. const handleFeatureClick = (feature, clickConfig) => {
  1082. if (clickConfig.action === 'popup') {
  1083. // 显示弹窗逻辑
  1084. showFeaturePopup(feature, clickConfig.popupConfig);
  1085. }
  1086. };
  1087. // 处理要素悬停事件
  1088. const handleFeatureHover = (feature, hoverConfig) => {
  1089. if (hoverConfig.action === 'highlight') {
  1090. // 高亮显示逻辑
  1091. highlightFeature(feature);
  1092. }
  1093. };
  1094. // 显示要素弹窗
  1095. const showFeaturePopup = (feature, popupConfig) => {
  1096. // 清除已存在的弹窗
  1097. clearPopups();
  1098. const properties = feature.getProperties();
  1099. const geometry = feature.getGeometry();
  1100. const coordinates = geometry.getCoordinates();
  1101. let center = coordinates
  1102. if (isArray(coordinates)) {
  1103. const extent = geometry.getExtent()
  1104. center = getCenter(extent); // 计算包围盒的中心点坐标
  1105. }
  1106. // 创建弹窗元素
  1107. const popupElement = document.createElement('div');
  1108. popupElement.className = 'feature-popup';
  1109. // 根据配置生成弹窗内容
  1110. if (popupConfig && popupConfig.template) {
  1111. popupElement.innerHTML = getPopupContentByTemplate(popupConfig.template, properties, popupElement);
  1112. } else {
  1113. // 生成默认弹窗内容
  1114. let content = `
  1115. <div class="popup-header">
  1116. <h3>${properties.name || properties['测站名称'] || '未知站点'}</h3>
  1117. <button class="popup-close" onclick="closePopup()">×</button>
  1118. </div>
  1119. <div class="popup-content">
  1120. <div class="popup-info">
  1121. `;
  1122. // 添加属性信息
  1123. Object.keys(properties).forEach(key => {
  1124. if (key !== 'geometry' && key !== 'name' && key !== 'stationName') {
  1125. content += `<div class="info-item"><span class="label">${key}:</span><span class="value">${properties[key]}</span></div>`;
  1126. }
  1127. });
  1128. content += `
  1129. </div>
  1130. </div>
  1131. `;
  1132. popupElement.innerHTML = content;
  1133. }
  1134. // 添加关闭按钮事件监听
  1135. const closeBtn = popupElement.querySelector('.popup-close');
  1136. if (closeBtn) {
  1137. closeBtn.onclick = () => {
  1138. clearPopups();
  1139. };
  1140. }
  1141. let popupOverlay = new Overlay({
  1142. element: popupElement,
  1143. positioning: popupConfig?.positioning || "bottom-center",
  1144. stopEvent: popupConfig?.stopEvent !== undefined ? popupConfig.stopEvent : true,
  1145. offset: popupConfig?.offset || [0, -10],
  1146. autoPan: true
  1147. });
  1148. popupOverlay.setPosition(center);
  1149. mapChart.value.addOverlay(popupOverlay);
  1150. popupOverlays.value.push(popupOverlay);
  1151. if (popupConfig && popupConfig.template) {
  1152. loadPopupChartByTemplate(popupConfig.template, properties);
  1153. }
  1154. };
  1155. // 高亮要素
  1156. const highlightFeature = (feature) => {
  1157. // 获取要素的原始样式
  1158. const originalStyle = feature.get('originalStyle') || feature.getStyle();
  1159. // 保存原始样式以便恢复
  1160. feature.set('originalStyle', originalStyle);
  1161. // 创建高亮样式
  1162. const highlightStyle = createDynamicStyle({
  1163. fill: {
  1164. color: 'rgba(255, 255, 0, 0.8)' // 黄色填充
  1165. },
  1166. stroke: {
  1167. color: '#ff0000', // 红色边框
  1168. width: 3
  1169. },
  1170. circle: {
  1171. radius: 8,
  1172. fill: {
  1173. color: 'rgba(255, 255, 0, 0.8)'
  1174. },
  1175. stroke: {
  1176. color: '#ff0000',
  1177. width: 3
  1178. }
  1179. }
  1180. });
  1181. // 应用高亮样式
  1182. feature.setStyle(highlightStyle);
  1183. };
  1184. let timerId = null
  1185. // 聚焦
  1186. function highlightPoint(center) {
  1187. // 先清除已存在的高亮图层
  1188. const existingHighlightOverlay = mapChart.value.getOverlays().getArray().find(ov => ov['id'] === 'highlight-point');
  1189. if (existingHighlightOverlay) {
  1190. mapChart.value.removeOverlay(existingHighlightOverlay);
  1191. if (timerId) {
  1192. clearTimeout(timerId);
  1193. }
  1194. }
  1195. // 创建 Overlay 并绑定元素
  1196. const overlayElement = document.createElement('div');
  1197. overlayElement.className = 'highlight-point-overlay';
  1198. const overlay = new Overlay({
  1199. id: 'highlight-point',
  1200. element: overlayElement,
  1201. position: center,
  1202. stopEvent: false,
  1203. zIndex: 1000,
  1204. offset: [-16, -18],
  1205. });
  1206. mapChart.value.addOverlay(overlay);
  1207. // 3秒后删掉 - 改进的删除方式
  1208. timerId = setTimeout(() => {
  1209. if (mapChart.value) {
  1210. try {
  1211. const overlayToRemove = mapChart.value.getOverlays().getArray().find(ov => ov['id'] === 'highlight-point');
  1212. if (overlayToRemove) {
  1213. mapChart.value.removeOverlay(overlayToRemove);
  1214. }
  1215. } catch (error) {
  1216. console.warn('删除高亮Overlay时出错:', error);
  1217. }
  1218. }
  1219. }, 3000)
  1220. }
  1221. // 清除所有弹窗
  1222. const clearPopups = () => {
  1223. popupOverlays.value.forEach(overlay => {
  1224. mapChart.value.removeOverlay(overlay);
  1225. });
  1226. popupOverlays.value = [];
  1227. };
  1228. // 跳转到新中心点
  1229. function toCenter(center, zoom, duration = 500, highlight) {
  1230. if (center && center.length >= 2) {
  1231. mapChart.value.getView().animate({
  1232. center: center,
  1233. zoom: zoom ? zoom : mapChart.value.getView().getZoom(),
  1234. duration: duration,
  1235. });
  1236. if (highlight) {
  1237. highlightPoint(center);
  1238. }
  1239. }
  1240. }
  1241. async function loadLayers(layers) {
  1242. for (const layer of layers) {
  1243. switch (layer.type) {
  1244. case 'vector':
  1245. await addVectorLayer(layer)
  1246. }
  1247. }
  1248. }
  1249. bus.on('show-map-position', ({latitude, longitude}) => {
  1250. toCenter([longitude, latitude], null, 1500, true)
  1251. })
  1252. // 实时更新地图
  1253. watch(() => props.config, async (config) => {
  1254. if (config) {
  1255. // 渲染配置
  1256. // 1. 跳转中心点
  1257. toCenter(config.center, config.zoom);
  1258. // 渲染图层
  1259. await loadLayers(config.layers);
  1260. }
  1261. }, {deep: true});
  1262. </script>
  1263. <style scoped lang="scss">
  1264. .biz-data-card-header {
  1265. width: 100%;
  1266. height: 36px;
  1267. background: url("@/assets/map/img/left-title.png") no-repeat;
  1268. background-size: 100% 100%;
  1269. font-size: 16px;
  1270. font-family: 'PuHuiTi', sans-serif;
  1271. font-weight: bolder;
  1272. color: #fff;
  1273. text-align: center;
  1274. line-height: 34px;
  1275. }
  1276. /*滚动条里面轨道*/
  1277. ::-webkit-scrollbar-track {
  1278. background-color: rgba(20, 19, 19, 0);
  1279. }
  1280. /*关键设置 tbody出现滚动条*/
  1281. ::-webkit-scrollbar-thumb {
  1282. background-color: rgba(58, 100, 179, 0.5);
  1283. border-radius: 8px 10px;
  1284. }
  1285. ::v-deep(.el-scrollbar) {
  1286. --el-scrollbar-bg-color: rgba(58, 100, 179);
  1287. --el-scrollbar-hover-bg-color: rgba(58, 100, 179);
  1288. }
  1289. .map-index {
  1290. height: 100%;
  1291. width: 100%;
  1292. position: relative;
  1293. #mapChart {
  1294. height: 100%;
  1295. width: 100%;
  1296. }
  1297. }
  1298. </style>
  1299. <style lang="scss">
  1300. .custom-popup {
  1301. background: rgba(255, 255, 255, 0.95);
  1302. border-radius: 8px;
  1303. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
  1304. border: 1px solid #3498db;
  1305. width: 172px;
  1306. .popup-title {
  1307. width: 100%;
  1308. line-height: 15px;
  1309. background-color: rgba(255, 23, 0);
  1310. padding: 10px;
  1311. text-align: center;
  1312. color: #fff;
  1313. font-size: 16px;
  1314. }
  1315. .popup-top {
  1316. padding: 5px 10px;
  1317. width: 100%;
  1318. background-color: rgba(58, 100, 179);
  1319. display: flex;
  1320. flex-direction: column;
  1321. color: #fff;
  1322. font-size: 14px;
  1323. }
  1324. .popup-bottom {
  1325. padding: 5px 10px;
  1326. width: 100%;
  1327. background-color: rgba(71, 146, 211);
  1328. display: flex;
  1329. flex-direction: column;
  1330. color: #fff;
  1331. font-size: 14px;
  1332. }
  1333. }
  1334. .feature-popup {
  1335. background: rgba(255, 255, 255, 0.95);
  1336. border-radius: 8px;
  1337. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
  1338. border: 1px solid #3498db;
  1339. width: 250px;
  1340. font-family: Arial, sans-serif;
  1341. .popup-header {
  1342. display: flex;
  1343. justify-content: space-between;
  1344. align-items: center;
  1345. background-color: rgba(58, 100, 179);
  1346. padding: 10px;
  1347. border-radius: 8px 8px 0 0;
  1348. h3 {
  1349. margin: 0;
  1350. color: white;
  1351. font-size: 14px;
  1352. }
  1353. .popup-close {
  1354. background: none;
  1355. border: none;
  1356. color: white;
  1357. font-size: 18px;
  1358. cursor: pointer;
  1359. }
  1360. }
  1361. .popup-content {
  1362. padding: 10px;
  1363. }
  1364. .popup-info {
  1365. .info-item {
  1366. display: flex;
  1367. justify-content: space-between;
  1368. margin-bottom: 5px;
  1369. font-size: 12px;
  1370. .label {
  1371. font-weight: bold;
  1372. color: #333;
  1373. }
  1374. .value {
  1375. color: #666;
  1376. }
  1377. }
  1378. }
  1379. .popup-chart {
  1380. border-top: 1px solid #eee;
  1381. padding-top: 10px;
  1382. canvas {
  1383. width: 100%;
  1384. }
  1385. }
  1386. }
  1387. .highlight-point-overlay {
  1388. width: 32px;
  1389. height: 32px;
  1390. background-image: url('@/assets/map/img/dyCenter.gif');
  1391. background-size: contain;
  1392. background-repeat: no-repeat;
  1393. }
  1394. </style>
  1395. <style scoped>
  1396. .horizontal-timeline-container {
  1397. width: 100%;
  1398. overflow-x: auto; /* 允许横向滚动 */
  1399. padding: 20px 0;
  1400. }
  1401. .horizontal-timeline {
  1402. display: flex;
  1403. min-width: 800px; /* 根据时间轴项的数量调整最小宽度 */
  1404. padding: 50px; /* 增加内边距 */
  1405. }
  1406. .timeline-item {
  1407. flex: 1; /* 每个项平均分配宽度 */
  1408. position: relative;
  1409. min-width: 200px; /* 每个项的最小宽度 */
  1410. }
  1411. .content-wrapper {
  1412. display: flex;
  1413. flex-direction: column;
  1414. text-align: center;
  1415. padding: 0 10px;
  1416. }
  1417. .content-title {
  1418. font-size: 16px;
  1419. margin: 10px 0;
  1420. }
  1421. .content-people {
  1422. color: #8c8c8c;
  1423. font-size: 14px;
  1424. }
  1425. /* 使用 :deep() 穿透修改 Element Plus 组件内部样式 */
  1426. :deep(.horizontal-timeline .el-timeline-item) {
  1427. flex: 1;
  1428. position: relative;
  1429. }
  1430. :deep(.horizontal-timeline .el-timeline-item__tail) {
  1431. border-left: none;
  1432. border-top: 2px solid #e4e7ed;
  1433. width: 100%;
  1434. position: absolute;
  1435. top: 12px;
  1436. left: 0;
  1437. }
  1438. :deep(.horizontal-timeline .el-timeline-item__node) {
  1439. position: absolute;
  1440. top: 6px;
  1441. left: 50%;
  1442. transform: translateX(-50%);
  1443. }
  1444. :deep(.horizontal-timeline .el-timeline-item__wrapper) {
  1445. padding-left: 0;
  1446. position: absolute;
  1447. top: 30px;
  1448. left: 50%;
  1449. transform: translateX(-50%);
  1450. text-align: center;
  1451. width: 100%;
  1452. }
  1453. /* 激活状态样式 */
  1454. .active :deep(.el-timeline-item__node) {
  1455. background-color: #409EFF; /* 使用 Element Plus 主色 */
  1456. }
  1457. .active :deep(.el-timeline-item__tail) {
  1458. border-color: #409EFF;
  1459. }
  1460. .tooltip {
  1461. position: absolute;
  1462. background: #409EFF;
  1463. color: white;
  1464. padding: 8px 12px;
  1465. border-radius: 4px;
  1466. width: 100px;
  1467. font-size: 12px;
  1468. pointer-events: none;
  1469. display: none; /* 初始隐藏 */
  1470. box-shadow: 0 2px 4px #409EFF;
  1471. max-width: 200px;
  1472. z-index: 1000;
  1473. }
  1474. .tooltip-header {
  1475. font-weight: bold;
  1476. margin-bottom: 4px;
  1477. border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  1478. padding-bottom: 2px;
  1479. }
  1480. .tooltip-content {
  1481. font-size: 11px;
  1482. opacity: 0.9;
  1483. }
  1484. </style>