| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606 |
- <template>
- <div class="map-index">
- <div id="mapChart"></div>
- <div class="map-left">
- <div class="left-title">模型列表</div>
- <div class="left-tree">
- <el-tree
- :data="data"
- :props="defaultProps"
- show-checkbox
- node-key="id"
- :default-expanded-keys="[1, 8]"
- :default-checked-keys="[1]"
- @node-click="handleNodeClick"
- @check-change="handleCheckChange"
- />
- </div>
- </div>
- <div class="map-right">
- <div class="right-title">模型数据展示</div>
- <div class="right-top-title">
- <img src="@/assets/map/img/站点.png"/>
- <span>预报站点信息</span>
- </div>
- <div class="station-table">
- <el-scrollbar style="height: 100%;">
- <table>
- <thead>
- <tr>
- <td class="table-index">序号</td>
- <td class="table-head">站码</td>
- <td class="table-head">站名</td>
- <td class="table-head">实时潮位</td>
- <td class="table-head">发生时间</td>
- <td class="table-head">警戒潮位</td>
- <td class="table-head">距离警戒</td>
- <td class="table-head">预报潮位</td>
- <td class="table-head">发生时间</td>
- <td class="table-head">距离警戒</td>
- </tr>
- </thead>
- <tbody>
- <tr
- v-for="(item,index) in StnmData.data"
- :key="index"
- >
- <td class="table-index">{{index+1}}</td>
- <td class="table-tbody">{{item.stationName}}</td>
- <td class="table-tbody">{{ item.stationCode}}</td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- <td class="table-tbody"></td>
- </tr>
- </tbody>
- </table>
- </el-scrollbar>
- </div>
- </div>
- <!-- 可拖拽图标组 -->
- <div
- class="map-fcicon"
- :style="{ left: fcPosition.x + 'px', top: fcPosition.y + 'px' }"
- v-show="showIcon"
- @mousedown="startDrag"
- ></div>
- <div
- class="dialog"
- v-if="showDialog"
- :style="{ left: dialogPosition.x + 'px', top: dialogPosition.y + 'px' }"
- >
- <div class="dialog-header" @mousedown="startDialogDrag">
- 风场
- <button class="close-btn" @click="closeDialog">×</button>
- </div>
- <div class="dialog-body"></div>
- </div>
- </div>
- </template>
- <script setup>
- import 'ol/css';
- import {ScaleLine, defaults as defaultControls} from 'ol/control';
- import Map from 'ol/Map';
- import View from 'ol/View';
- import TileLayer from "ol/layer/Tile";
- import { XYZ, Vector as VectorSource } from 'ol/source.js';
- import VectorLayer from "ol/layer/Vector";
- import { LineString, Point } from "ol/geom";
- import { Icon, Style, Text,Circle } from 'ol/style';
- import Fill from "ol/style/Fill";
- import Feature from 'ol/Feature';
- import Stroke from "ol/style/Stroke";
- import Overlay from 'ol/Overlay';
- import StnmData from "@/assets/map/json/stnmData.json";
- import red_trangle from "@/assets/map/img/Ⅳ.png";
- const mapChart = ref(null);
- const mapCenter = ref([121.472644, 31.231706]);
- const mapZoom = ref(9);
- const data = [
- {
- id:1,
- label: '上海市城区洪涝仿真模型',
- children: [
- {
- id:8,
- label: '上海市城区洪涝仿真模型-1',
- },
- ],
- },
- {
- id:2,
- label: '上海沿海风暴潮预报模型',
- },
- {
- id:3,
- label: '黄浦江水系水文分析预报数值模拟',
- },
- {
- id:4,
- label: '苏州河水系水情预报模型',
- },
- {
- id:5,
- label: '内涝风险实时预警与预报',
- },
- {
- id:6,
- label: '上海市中心城区排水系统模型',
- },
- {
- id:7,
- label: '温带风暴潮预报模型',
- },
- ]
- const defaultProps = {
- children: 'children',
- label: 'label',
- }
- const stnmVectorLayer = ref(null);
- const popupOverlays = ref([]);// 存储弹窗的引用
- const showIcon = ref(true);
- const showDialog = ref(false);
- const isDragging = ref(false);
-
- const fcPosition = ref({ x: 280, y: 40 });
- const dialogPosition = ref({ x: 0, y: 0 });
-
- const dragStartPosition = ref({ x: 0, y: 0 });
- const isDraggingDialog = ref(false);
- const dialogDragStart = ref({ x: 0, y: 0 });
- onMounted(() => {
- initMap();
- console.log('StnmData',StnmData);
-
- });
- const initMap = () => {
- let vecLayer = new TileLayer({
- source: new XYZ({
- url: "http://t0.tianditu.gov.cn/vec_w/wmts?" +
- "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
- "&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" +
- "&tk=9bb941214f10fbf9a3eab43f45cb2b7e",
- }),
- });
- let cvaLayer = new TileLayer({
- source: new XYZ({
- url: "http://t0.tianditu.gov.cn/cva_w/wmts?" +
- "SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles" +
- "&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" +
- "&tk=9bb941214f10fbf9a3eab43f45cb2b7e",
- }),
- });
- mapChart.value= new Map({
- target: 'mapChart',
- view: new View({
- center: mapCenter.value,
- zoom: mapZoom.value,
- minZoom: 3,
- maxZoom: 16,
- projection: 'EPSG:4326',
- }),
- layers: [vecLayer, cvaLayer],
- controls: defaultControls({
- zoom: false,//不显示放大放小按钮
- rotate: false,//不显示指北针控件
- attribution: false,//不显示右下角的地图信息控件
- scaleLine:false,//不显示比例尺控件
- })
- });
- addStnm();
- };
- const addStnm = () => {
- let features = [];
- let stationPopData = [];
- const targetStations = ['绿华山', '横沙', '崇西闸', '金山嘴'];
- StnmData.data.forEach(item => {
- targetStations.includes(item.stationName) && stationPopData.push(item);
- let point = new Point([item.lgtd, item.lttd]);
- let feature = new Feature({
- geometry: point,
- name: item.stationName,
- properties: item,
- });
- var style = new Style({
- image: new Icon({
- anchor: [0.5, 0.5],
- scale: 0.08,
- src: red_trangle,
- }),
- });
- feature.setStyle(style);
- features.push(feature);
- })
- let vectorSource = new VectorSource({
- features: features,
- });
- stnmVectorLayer.value = new VectorLayer({
- source: vectorSource,
- });
- mapChart.value.addLayer(stnmVectorLayer.value);
- showPopupInfo(stationPopData);
- };
- // 显示地图坐标信息
- const showPopupInfo = (data) => {
- data.forEach((station, index) => {
- // 创建弹窗元素
- const popupElement = document.createElement('div');
- popupElement.className = 'custom-popup';
- popupElement.id = `popup_${station.stationCode}`;
- // 设置弹窗内容
- popupElement.innerHTML = `
- <div class="popup-title">${station.stationName} (${station.stationCode})</div>
- <div class="popup-top">
- <span>实时潮位:<span></span></span>
- <span>发生时间:<span></span></span>
- <span>警戒潮位:<span></span></span>
- <span>距离警戒:<span></span></span>
- </div>
- <div class="popup-bottom">
- <span>预报潮位:<span></span></span>
- <span>发生时间:<span></span></span>
- <span>距离警戒:<span></span></span>
- </div>
- `;
- let popupOverlay = new Overlay({
- element: popupElement,
- positioning: "top-center",
- stopEvent: false,
- offset: [0, -200],
- });
- const coordinate = [station.lgtd, station.lttd];
- popupOverlay.setPosition(coordinate);
- mapChart.value.addOverlay(popupOverlay);
- // 保存引用
- popupOverlays.value.push(popupOverlay);
- });
- };
- // 清除所有弹窗
- const clearPopups = () => {
- popupOverlays.value.forEach(overlay => {
- mapChart.value.removeOverlay(overlay);
- });
- popupOverlays.value = [];
- };
- const startDrag = (event) => {
- isDragging.value = true;
- dragStartPosition.value = {
- x: event.clientX - fcPosition.value.x,
- y: event.clientY - fcPosition.value.y
- };
-
- window.addEventListener('mousemove', onMouseMove);
- window.addEventListener('mouseup', onMouseUp);
- };
- const onMouseMove = (event) => {
- if (!isDragging.value) return;
-
- fcPosition.value = {
- x: event.clientX - dragStartPosition.value.x,
- y: event.clientY - dragStartPosition.value.y
- };
- dialogPosition.value = {
- x:fcPosition.value.x,
- y:fcPosition.value.y
- }
- };
- const onMouseUp = () => {
- if (isDragging.value) {
- showIcon.value = false;
- showDialog.value = true;
- isDragging.value = false;
- window.removeEventListener('mousemove', onMouseMove);
- window.removeEventListener('mouseup', onMouseUp);
- }
- };
- const startDialogDrag = (event) => {
- isDraggingDialog.value = true;
- dialogDragStart.value = {
- x: event.clientX - dialogPosition.value.x,
- y: event.clientY - dialogPosition.value.y
- };
-
- window.addEventListener('mousemove', onDialogMouseMove);
- window.addEventListener('mouseup', onDialogMouseUp);
- };
- const onDialogMouseMove = (event) => {
- if (!isDraggingDialog.value) return;
-
- dialogPosition.value = {
- x: event.clientX - dialogDragStart.value.x,
- y: event.clientY - dialogDragStart.value.y
- };
- };
-
- const onDialogMouseUp = () => {
- isDraggingDialog.value = false;
- window.removeEventListener('mousemove', onDialogMouseMove);
- window.removeEventListener('mouseup', onDialogMouseUp);
- };
-
- const closeDialog = () => {
- showDialog.value = false;
- showIcon.value = true;
- fcPosition.value = {
- x: 280,
- y: 40
- }
- };
-
- onUnmounted(() => {
- window.removeEventListener('mousemove', onMouseMove);
- window.removeEventListener('mouseup', onMouseUp);
- window.removeEventListener('mousemove', onDialogMouseMove);
- window.removeEventListener('mouseup', onDialogMouseUp);
- });
- </script>
- <style scoped lang="scss">
- /*滚动条里面轨道*/
- ::-webkit-scrollbar-track{
- background-color: rgba(20, 19, 19,0);
- }
- /*关键设置 tbody出现滚动条*/
- ::-webkit-scrollbar-thumb{
- background-color: rgba(58, 100, 179,0.5);
- border-radius: 8px 10px;
- }
- ::v-deep(.el-scrollbar) {
- --el-scrollbar-bg-color: rgba(58, 100, 179);
- --el-scrollbar-hover-bg-color: rgba(58, 100, 179);
- }
- .map-index{
- height: 100%;
- width: 100%;
- position: relative;
- #mapChart{
- height: 100%;
- width: 100%;
- }
- .map-left{
- position: absolute;
- top: 1%;
- left: 0.5%;
- height: 98%;
- width: 260px;
- background:rgba(255,255,255,0.9);
- border-radius: 8px;
- .left-title{
- width: 100%;
- height: 36px;
- background: url("@/assets/map/img/left-title.png") no-repeat;
- background-size: 100% 100%;
- font-size: 16px;
- font-family: 'PuHuiTi', sans-serif;
- font-weight: bolder;
- color: #fff;
- text-align: center;
- line-height: 34px;
- }
- }
- .map-right{
- position: absolute;
- top: 1%;
- right: 0.5%;
- height: 98%;
- width: 400px;
- background:rgba(255,255,255,0.9);
- border-radius: 8px;
- .right-title{
- width: 100%;
- height: 36px;
- background: url("@/assets/map/img/left-title.png") no-repeat;
- background-size: 100% 100%;
- font-size: 16px;
- font-family: 'PuHuiTi', sans-serif;
- font-weight: bolder;
- color: #fff;
- text-align: center;
- line-height: 34px;
- }
- .right-top-title{
- width: 100%;
- line-height: 30px;
- padding: 10px;
- display: flex;
- img{
- width: 30px;
- height:30px;
- }
- span{
- color: #000;
- font-size: 16px;
- font-family: 'PuHuiTi', sans-serif;
- font-weight: bolder;
- margin-left: 10px;
- }
- }
- }
- }
- .station-table{
- width: 94%;
- margin:0 3%;
- .table-index{
- width: 40px;
- }
- .table-head{
- width: 80px;
- }
- table{
- width: 100%;
- border-spacing: 0px;
- border-collapse: collapse; /* 设置表格边框合并为单线 */
- border-top: 2px solid #82bcfd;
- border-bottom: 2px solid #82bcfd;
- thead{
- background-image: -webkit-linear-gradient(top, #ebf5ff, #fff);
- color:#000;
- font-size:14px;
- font-family: 'PuHuiTi', sans-serif;
- font-weight: bold;
- width: 100%;
- border-left: 1px solid #d3e8f9;
- border-right: 1px solid #d3e8f9;
- border-bottom: 1px solid #d3e8f9;
- td{
- text-align: center;
- padding: 10px 0;
- }
- }
- tbody{
- color:#000;
- font-size:12px;
- font-family: 'PuHuiTi', sans-serif;
- width: 100%;
- border: 1px solid #d3e8f9;
- border-top:none;
- tr{
- line-height: 28px;
- background-image: -webkit-linear-gradient(top, #d7eaff, #fff);
- }
- td{
- /* border-bottom: 1px solid #d4f0fc; */
- text-align: center;
- padding: 5px 0;
- }
- }
- }
- table tbody {
- display: block;
- height: 20vh !important;
- overflow-y: scroll;
- }
- table thead,tbody tr {
- display: table;
- width: 100%;
- table-layout: fixed;
- }
- }
- .map-fcicon {
- position: absolute;
- width: 40px;
- height: 40px;
- background-image: url("@/assets/map/img/风场.png");
- background-repeat: no-repeat;
- background-size: 50%;
- background-position: center;
- background-color: rgba(72, 174, 228);
- color: white;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: grab;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- transition: transform 0.2s, box-shadow 0.2s;
- z-index: 10;
- .fc-img{
- width: 25px;
- height: 25px;
- }
- }
-
- .map-fcicon:hover {
- transform: scale(1.1);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
- }
-
- .map-fcicon:active {
- cursor: grabbing;
- }
-
- .dialog {
- position: absolute;
- width: 300px;
- background: white;
- border-radius: 10px;
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
- overflow: hidden;
- z-index: 100;
- }
-
- .dialog-header {
- padding: 5px;
- background: rgba(52, 152, 219,0.8);
- color: white;
- cursor: move;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .dialog-body {
- padding: 20px;
- color: #2c3e50;
- }
-
- .close-btn {
- background: none;
- border: none;
- color: white;
- font-size: 20px;
- cursor: pointer;
- }
-
- .drop-indicator {
- position: absolute;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- padding: 10px 20px;
- background: #2ecc71;
- color: white;
- border-radius: 20px;
- opacity: 0;
- transition: opacity 0.3s;
- }
-
- .drop-indicator.visible {
- opacity: 1;
- }
- </style>
- <style lang="scss">
- .custom-popup {
- background: rgba(255, 255, 255, 0.95);
- border-radius: 8px;
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
- border: 1px solid #3498db;
- width: 172px;
- .popup-title{
- width: 100%;
- line-height: 15px;
- background-color: rgba(255, 23, 0);
- padding: 10px;
- text-align: center;
- color: #fff;
- font-size: 16px;
- }
- .popup-top{
- padding:5px 10px;
- width: 100%;
- background-color: rgba(58, 100, 179);
- display: flex;
- flex-direction: column;
- color: #fff;
- font-size: 14px;
- }
- .popup-bottom{
- padding:5px 10px;
- width: 100%;
- background-color: rgba(71, 146, 211);
- display: flex;
- flex-direction: column;
- color: #fff;
- font-size: 14px;
- }
- }
- </style>
|