| | |
| | | <template> |
| | | <div class="page-container"> |
| | | <div class="map" id="map"></div> |
| | | |
| | | <div class="weather-box" v-show="weatherShow"> |
| | | <div class="icon"> |
| | | <van-image :src="WEATHER?.[weather] || ''" width="100%" height="100%"></van-image> |
| | | </div> |
| | | |
| | | <div class="content"> |
| | | <span class="tq">{{ weather }} {{ temperature }}</span> |
| | | <span class="tq">风速:{{ windVelocity }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="back-btn" @click="goBackWork" v-show="workNavigationShow"> |
| | | <van-image width="20" height="20" :src="backIcon" /> |
| | | <div class="label">返回</div> |
| | | </div> |
| | | <div class="layer-btn" @click="layerChangePopupShow = true" v-show="layerShow"> |
| | | <van-image width="20" height="20" :src="layerIcon" /> |
| | | <div class="label">图层</div> |
| | | </div> |
| | | |
| | | <div class="location-btn" @click="setMapLocation" v-show="locationShow"> |
| | | <van-image width="20" height="20" :src="locationIcon" /> |
| | | <div class="label">定位</div> |
| | | </div> |
| | | <!-- 事件导航按钮 --> |
| | | <div class="work-btn" v-show="workNavigationShow"> |
| | | <van-button round block type="primary" color="#1D6FE9" @click="openAmap">事件导航</van-button> |
| | | </div> |
| | | <slot name="searchBar"></slot> |
| | | <slot name="eventNav"></slot> |
| | | |
| | | <van-popup v-model:show="layerChangePopupShow" position="bottom" round> |
| | | <div class="popup-container"> |
| | | <div class="category-content"> |
| | | <div class="header"> |
| | | <div class="title">底图切换</div> |
| | | </div> |
| | | |
| | | <div class="content"> |
| | | <div |
| | | class="layer-box" |
| | | :class="{ on: setSelectMapLayerKey === item.key }" |
| | | v-for="(item, ind) in layers" |
| | | :key="ind" |
| | | @click="setMapLayer(item)" |
| | | > |
| | | <div class="image"> |
| | | <van-image :src="item.src" width="100%" height="100%"></van-image> |
| | | </div> |
| | | <div class="label">{{ item.name }}</div> |
| | | </div> |
| | | </div> |
| | | <div class="location-btn" @click="setMapLocation" v-show="locationShow"> |
| | | <van-image width="20" height="20" :src="locationIcon" /> |
| | | <div class="label">定位</div> |
| | | </div> |
| | | </div> |
| | | </van-popup> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { searchGeocoder } from '@/utils/util' |
| | | import mapnavIcon from '@/appDataSource/appwork/mapnav.svg' |
| | | import backIcon from '@/appDataSource/leafletMapIcon/back-icon.svg' |
| | | import incidentPoint from '@/appDataSource/appwork/positioning.svg' |
| | | import droneIcon from '@/appDataSource/leafletMapIcon/drone-icon.svg' |
| | | import userLocationIcon from '@/appDataSource/leafletMapIcon/user-location.svg' |
| | | import layerIcon from '@/appDataSource/leafletMapIcon/layer-icon.svg' |
| | | import locationIcon from '@/appDataSource/leafletMapIcon/location-icon.svg' |
| | | import { useStore } from 'vuex' |
| | | import { getDroneSuggest } from '@/api/home/common' |
| | | import DroneMarkerPopup from '@/appComponents/DroneMarkerPopup/index.vue' |
| | | import EventMarkerPopup from '@/appComponents/EventMarkerPopup/index.vue' |
| | | import { basemapLayer0, basemapLayer1, basemapLayer2, basemapLayer3 } from '@/const/leafletConst' |
| | | |
| | | import L from 'leaflet' |
| | |
| | | import sl from '@/appDataSource/leafletMapIcon/sl.svg' |
| | | import yx from '@/appDataSource/leafletMapIcon/yx.svg' |
| | | import EventBus from '@/utils/eventBus' |
| | | import { polygonTransformScale } from '@/utils/turfFunc' |
| | | import { getEventImage } from '@/utils/stateToImageMap/event' |
| | | import { getDroneStatusImage } from '@/utils/stateToImageMap/drone' |
| | | import { WEATHER } from '@/const/weather' |
| | | |
| | | import { wgs84ToGcj02 } from '@/utils/coordinateTransformation' |
| | | const emit = defineEmits(['location-selected']) |
| | | import _ from 'lodash' |
| | | import { useAreaBoundary } from '@/hooks/useAreaBoundary' |
| | | const { weatherShow, layerShow, locationShow, createWorkShow, workNavigationShow, mapCurrentDetail, clearMarker } = |
| | | const { locationShow, createWorkShow, workNavigationShow, mapCurrentDetail, clearMarker } = |
| | | defineProps({ |
| | | weatherShow: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | layerShow: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | locationShow: { |
| | | type: Boolean, |
| | | default: false, |
| | |
| | | }, |
| | | ] |
| | | |
| | | const layerChangePopupShow = ref(false) |
| | | const setSelectMapLayerKey = ref(1) // 选择的地图图层 |
| | | let boundaryInstance = useAreaBoundary({ initFly: true }) |
| | | let markersLayer = null |
| | | const initMap = () => { |
| | | if (map) return |
| | |
| | | doubleClickZoom: false, |
| | | editable: true, //绘制控件 |
| | | }).setView([25.992338, 114.823254], 13) |
| | | boundaryInstance.initBoundary(map) |
| | | |
| | | markersLayer = L.layerGroup().addTo(map) // 创建一个标注层,便于管理和移除 |
| | | if (createWorkShow) { |
| | |
| | | const store = useStore() |
| | | const selectedAreaCode = computed(() => store.state.user.selectedAreaCode) |
| | | |
| | | const weather = ref('') |
| | | // 风速 |
| | | const windVelocity = ref('') |
| | | const temperature = ref('') |
| | | // 获取天气建议 |
| | | const getWeatherSuggest = () => { |
| | | getDroneSuggest({ areaCode: selectedAreaCode.value }).then(res => { |
| | | if (res.data.code !== 0) return |
| | | weather.value = res.data.data.weather |
| | | windVelocity.value = res.data.data.windPower |
| | | temperature.value = res.data.data.temperature |
| | | }) |
| | | } |
| | | const goBackWork = () => { |
| | | const transmitData = { data: { type: 'workDetailback' } } |
| | | wx.miniProgram.navigateBack({ |
| | | delta: 1, |
| | | }) |
| | | wx.miniProgram.postMessage(transmitData) |
| | | uni.postMessage(transmitData) |
| | | |
| | | markersLayer.removeLayer(incidentMarker) |
| | | } |
| | | // 存储事件点标记实例,用于后续更新或移除 |
| | | let incidentMarker = null |
| | | |
| | |
| | | { immediate: true, deep: true } |
| | | ) |
| | | |
| | | const openAmap = () => { |
| | | if (!mapCurrentDetailData.value.longitude || !mapCurrentDetailData.value.latitude) { |
| | | showToast('未获取到事件坐标') |
| | | return |
| | | } |
| | | // wgs84ToGcj02(Number(item.longitude), Number(item.latitude)) |
| | | // wgs84ToGcj02(mapCurrentDetailData.value.longitude, mapCurrentDetailData.value.latitude) |
| | | const [gcjLng, gcjLat] = wgs84ToGcj02( |
| | | Number(mapCurrentDetailData.value.longitude), |
| | | Number(mapCurrentDetailData.value.latitude) |
| | | ) |
| | | |
| | | // 高德地图URL Scheme |
| | | const url = `https://uri.amap.com/marker?position=${gcjLng},${gcjLat}&name=%E4%BA%8B%E4%BB%B6%E7%82%B9&from='ztzf'` |
| | | |
| | | const transmitData = { data: { type: 'browser', data: { url: url } } } |
| | | wx.miniProgram.navigateTo({ url: `/subPackages/browser/index?url=${encodeURIComponent(url)}` }) |
| | | wx.miniProgram.postMessage(transmitData) |
| | | uni.postMessage(transmitData) |
| | | } |
| | | onMounted(async () => { |
| | | getWeatherSuggest() |
| | | |
| | | await nextTick() |
| | | initMap() |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | // 事件导航按钮 |
| | | .work-btn { |
| | | position: absolute; |
| | | bottom: 80px; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 996; |
| | | padding: 0 16px; |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .work-btn :deep(.van-button) { |
| | | width: 100%; |
| | | max-width: 194px; |
| | | height: 38px; |
| | | } |
| | | |
| | | .weather-box { |
| | | padding: 4px 0; |
| | | padding-right: 6px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | position: absolute; |
| | | top: 12px; |
| | | left: 12px; |
| | | |
| | | height: 48px; |
| | | background: #fff; |
| | | z-index: 996; |
| | | |
| | | box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.18); |
| | | border-radius: 6px 6px 6px 6px; |
| | | |
| | | .icon { |
| | | margin-left: 10px; |
| | | width: 24px; |
| | | height: 24px; |
| | | } |
| | | |
| | | .content { |
| | | height: 100%; |
| | | margin-left: 4px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-around; |
| | | |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 12px; |
| | | color: #757575; |
| | | text-align: left; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | } |
| | | |
| | | .tq { |
| | | margin: 0 5px; |
| | | } |
| | | .qk { |
| | | margin-left: 5px; |
| | | color: #04f043; |
| | | } |
| | | .yellowqk { |
| | | margin-left: 5px; |
| | | color: yellow; |
| | | } |
| | | .redqk { |
| | | margin-left: 5px; |
| | | color: red; |
| | | } |
| | | } |
| | | |
| | | .location-btn, |
| | | .layer-btn, |
| | | .back-btn { |
| | | .location-btn{ |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | |
| | | font-size: 10px; |
| | | } |
| | | } |
| | | .back-btn { |
| | | bottom: 185px; |
| | | } |
| | | .layer-btn { |
| | | bottom: 130px; |
| | | } |
| | | |
| | | .location-btn { |
| | | bottom: 76px; |
| | | } |
| | | } |
| | | |
| | | .popup-container { |
| | | padding: 10px 22px; |
| | | background: #fff; |
| | | border-radius: 12px 12px 0 0; |
| | | |
| | | .category-content { |
| | | .header { |
| | | height: 20px; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | color: #222324; |
| | | line-height: 20px; |
| | | text-align: left; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | } |
| | | |
| | | .content { |
| | | padding: 12px; |
| | | display: flex; |
| | | background: #fff; |
| | | border-radius: 16px; |
| | | |
| | | .layer-box { |
| | | margin-left: calc((100% / 3 - 84px) * 3 / 2); |
| | | |
| | | &:first-child { |
| | | margin-left: 0; |
| | | } |
| | | |
| | | &.on { |
| | | .image { |
| | | position: relative; |
| | | |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | border-radius: 4px 4px 4px 4px; |
| | | border: 4px solid #1d6fe9; |
| | | z-index: 1; |
| | | box-sizing: border-box; |
| | | } |
| | | } |
| | | |
| | | .label { |
| | | color: #1d6fe9; |
| | | } |
| | | } |
| | | |
| | | .image { |
| | | width: 84px; |
| | | height: 82px; |
| | | border-radius: 4px 4px 4px 4px; |
| | | overflow: hidden; |
| | | box-sizing: border-box; |
| | | border: none; |
| | | } |
| | | |
| | | .label { |
| | | margin-top: 6px; |
| | | height: 20px; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #222324; |
| | | line-height: 20px; |
| | | text-align: center; |
| | | font-style: normal; |
| | | text-transform: none; |
| | | } |
| | | } |
| | | |
| | | .layer-box:nth-child(3n + 1) { |
| | | margin-left: 0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |