feat: 弹窗和动效的实现

This commit is contained in:
Xu Zhimeng 2023-06-16 18:58:31 +08:00
parent dff9ee4442
commit 94ae30d3d6
7 changed files with 459 additions and 99 deletions

14
src/utils/map.js Normal file
View File

@ -0,0 +1,14 @@
/**
* 将十进制度格式的经度转换为度分秒
* @param {*} decimal
* @param {*} isLongitude 是不是经度
* @returns
*/
export function decimalToDms(decimal, isLongitude = true) {
const degrees = Math.floor(decimal);
const decimalMinutes = (decimal - degrees) * 60;
const minutes = Math.floor(decimalMinutes);
const seconds = parseInt((decimalMinutes - minutes) * 60);
const tail = isLongitude ? (decimal > 0 ? 'E' : decimal < 0 ? 'W' : '') : (decimal > 0 ? 'N' : decimal < 0 ? 'S' : '')
return `${Math.abs(degrees)}°${minutes}'${seconds}"${tail}`;
}

View File

@ -10,9 +10,11 @@
<label>Station Type:</label>
<span>{{ item.stationType }}</span>
</div>
</div>
<div class="data-list-item-children">
<div class="data-list-item-child" style="word-break: break-all">
<label>Altitude:</label>
<span>{{ item.altitude + 'm' }}</span>
<span>{{ item.altitude }}</span>
</div>
</div>
<div class="data-list-item-children">

View File

@ -1,19 +1,27 @@
<template>
<div ref="mapPopupRef" class="popover">
这是弹窗
<div ref="mapPopupRef" class="facility-info-popover">
<h2>{{ popupTitle }} Info</h2>
<a-spin :spinning="isGettingInfo">
<div class="facility-info-item" v-for="(item, index) in columns" :key="index">
<div class="facility-info-item-label">{{ item.label }}</div>
<div class="facility-info-item-content">{{ currStationInfo[item.key] || '--' }}</div>
</div>
</a-spin>
</div>
</template>
<script>
import { Vector as VectorLayer } from 'ol/layer'
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import { Fill, Icon, Stroke, Style } from 'ol/style'
import { Point } from 'ol/geom'
import Overlay from 'ol/Overlay'
import { MarkerIcon } from './markerEnum'
import { fromLonLat } from 'ol/proj'
import PopupColumns from './markerPopupColumns'
import { getAction } from '../../../api/manage'
import { debounce } from 'lodash'
import { decimalToDms } from '@/utils/map'
const POPUP_OVERLAY_ID = 'map_popup'
export default {
props: {
list: {
@ -23,50 +31,76 @@ export default {
},
data() {
return {
currStationInfo: {}
currStationInfo: {},
isGettingInfo: false,
columns: {},
popupTitle: ''
}
},
mounted() {
this.map = this.$parent.getMapInstance()
this.initLayer()
this.initMarkers()
this.initMapClick()
this.initMapPopup()
this.getStationInfo = debounce(stationInfo => {
//
if (this.isHover) {
this._getStationInfo(stationInfo)
}
}, 500)
},
methods: {
initLayer() {
this.markerLayer = new VectorLayer({
source: new VectorSource({
features: []
}),
properties: { name: 'eventMarker' }
})
this.map.addLayer(this.markerLayer)
console.log('%c [ ]-46', 'font-size:13px; background:pink; color:#bf2c9f;', this.markerLayer)
},
// marker
initMarkers() {
const markerFeatures = []
this.list.forEach(eventItem => {
markerFeatures.push(this.getMarker(eventItem))
this.map.addOverlay(this.getMarker(eventItem))
})
this.markerLayer.getSource().addFeatures(markerFeatures)
},
//
initMapClick() {
this.map.on('click', evt => {
const feature = this.map.forEachFeatureAtPixel(evt.pixel, feature => {
return feature
})
const stationInfo = feature && feature.values_ && feature.values_.stationInfo
if (stationInfo) {
this.showMapPopup(stationInfo)
} else {
this.closeMapPopup()
}
// marker
getMarker(stationInfo) {
const { lon, lat } = stationInfo
const img = document.createElement('img')
img.src = MarkerIcon[stationInfo.stationType]
img.addEventListener('click', () => {
this.$emit('markerClick')
})
img.addEventListener('mouseover', () => {
this.isHover = true
this.showMapPopup(stationInfo)
})
img.addEventListener('mouseout', () => {
this.isHover = false
this.closeMapPopup()
})
return new Overlay({
position: fromLonLat([lon, lat]),
element: img,
id: stationInfo.stationId,
positioning: 'center-center'
})
},
initRipples() {
this.list.forEach(eventItem => {
this.map.addOverlay(this.getRipple(eventItem))
})
},
getRipple({ lon, lat, stationId }) {
const rippleDiv = document.createElement('div')
rippleDiv.className = 'custom-ripple'
rippleDiv.innerHTML = ` <div class="inner-ripple-1"></div>
<div class="inner-ripple-2"></div>
`
return new Overlay({
position: fromLonLat([lon, lat]),
element: rippleDiv,
id: 'ripple_' + stationId,
positioning: 'center-center'
})
},
@ -74,69 +108,153 @@ export default {
initMapPopup() {
this.popupOverlay = new Overlay({
element: this.$refs.mapPopupRef,
autoPan: true,
autoPanAnimation: {
duration: 250
},
positioning: 'top-center'
positioning: 'top-center',
id: POPUP_OVERLAY_ID,
offset: [0, 27]
})
this.map.addOverlay(this.popupOverlay)
},
//
showMapPopup(stationInfo) {
async showMapPopup(stationInfo) {
this.popupOverlay.setPosition(fromLonLat([stationInfo.lon, stationInfo.lat]))
this.currStationInfo = stationInfo //
this.columns = PopupColumns[stationInfo.stationType]
this.popupTitle = stationInfo.stationType
this.isGettingInfo = true
this.getStationInfo(stationInfo)
},
//
async _getStationInfo(stationInfo) {
try {
const { success, result, message } = await getAction('/jeecg-station-operation/stationOperation/findInfo', {
stationId: stationInfo.stationId,
type: stationInfo.stationType
})
if (success) {
result.lon = decimalToDms(result.lon || result.longitude)
result.lat = decimalToDms(result.lat || result.latitude, false)
this.currStationInfo = result
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
} finally {
this.isGettingInfo = false
}
},
//
closeMapPopup() {
this.popupOverlay.setPosition(null)
},
// marker
getMarker(stationInfo) {
const { lon, lat } = stationInfo
const markerFeature = new Feature({
geometry: new Point(fromLonLat([lon, lat])),
stationInfo
})
markerFeature.setStyle(this.getMarkerStyle(stationInfo.stationType))
return markerFeature
},
// marker
getMarkerStyle(type) {
const src = MarkerIcon[type]
return new Style({
image: new Icon({
src,
scale: 0.8
})
})
},
// marker
setMarkerPosition(markerId, position) {
const overlay = this.map.getOverlayById(markerId)
overlay.setPosition(position)
}
},
watch: {
list() {
this.markerLayer.getSource().clear()
console.log('%c [ ]-122', 'font-size:13px; background:pink; color:#bf2c9f;', this.map)
this.map.getOverlays().clear()
this.initMapPopup()
this.initMarkers()
this.initRipples()
}
}
}
</script>
<style lang="less" scoped>
.popover {
width: 200px;
height: 300px;
background-color: rgba(2, 26, 29, 0.9);
box-shadow: 0 0 5px rgba(2, 26, 29, 0.9);
.facility-info {
&-popover {
position: relative;
width: 350px;
background-color: rgba(2, 26, 29, 0.9);
border-radius: 4px;
box-shadow: 0 0 5px rgba(2, 26, 29, 0.9);
padding: 5px;
&::before {
position: absolute;
top: -6px;
left: 50%;
content: '';
width: 12px;
height: 12px;
border: 1px solid rgba(2, 26, 29, 0.9);
background: rgba(2, 26, 29, 0.9);
transform: translateX(-50%) rotate(45deg) skew(14deg, 14deg);
border-bottom: 0;
border-right: 0;
}
h2 {
color: #6ebad0;
font-size: 16px;
text-align: center;
line-height: 30px;
border-bottom: 1px solid #0a544e;
margin-bottom: 0;
}
}
@border: 1px solid #0a544e;
&-item {
display: flex;
border: @border;
border-top: 0;
line-height: 25px;
&-label,
&-content {
width: 50%;
padding: 0 3px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-label {
border-right: @border;
color: #6ebad0;
}
}
}
</style>
<style lang="less">
.custom-ripple {
width: 85px;
height: 85px;
z-index: -1;
@duration: 1.8s;
@delay: 0.9s;
.inner-ripple-1 {
position: absolute;
top: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background-image: radial-gradient(circle, transparent 10%, rgba(15, 148, 28, 0.1) 30%, rgba(15, 148, 28, 0.5) 60%);
animation: rippleEffect @duration linear @delay infinite;
}
.inner-ripple-2 {
position: absolute;
top: 0;
width: 100%;
height: 100%;
border-radius: 50%;
background-image: radial-gradient(circle, transparent 10%, rgba(15, 148, 28, 0.1) 30%, rgba(15, 148, 28, 0.5) 60%);
animation: rippleEffect @duration linear @delay * 2 infinite;
}
}
@keyframes rippleEffect {
0% {
transform: scale(0.6);
opacity: 1;
}
100% {
transform: scale(1.2);
opacity: 0;
}
}
</style>

View File

@ -224,6 +224,11 @@ import { postAction } from '../../../api/manage'
import { MarkerType } from './markerEnum'
import { Vector as VectorLayer } from 'ol/layer'
import VectorSource from 'ol/source/Vector'
import { Circle } from 'ol/geom'
import { fromLonLat } from 'ol/proj'
import Feature from 'ol/Feature'
import { Fill, Stroke, Style } from 'ol/style'
// Filter
const filterList = [
@ -389,17 +394,26 @@ export default {
created() {
this.initParentMapProps()
document.addEventListener('fullscreenchange', this.onFullScreenChange)
this.stationList = []
},
destroyed() {
document.removeEventListener('fullscreenchange', this.onFullScreenChange)
},
methods: {
initParentMapProps() {
const { getZoom, setZoom, maxZoom, minZoom } = this.$parent
const { getZoom, setZoom, maxZoom, minZoom, map } = this.$parent
this.getZoom = getZoom
this.setZoom = setZoom
this.maxZoom = maxZoom
this.minZoom = minZoom
this.map = map
this.circleLayer = new VectorLayer({
source: new VectorSource({
features: []
}),
properties: { name: 'eventCircle' }
})
this.map.addLayer(this.circleLayer)
},
handleFullScreen() {
@ -420,10 +434,13 @@ export default {
this.active = active
switch (active) {
case 1: //
this.drawCircle()
this.emitStationChange()
break
case 2: //
this.emitTypeFilter()
const source = this.circleLayer.getSource()
source.clear()
break
}
},
@ -485,17 +502,19 @@ export default {
})
this.dataSource = data // Infomation
const stationList = [] //
markerList.forEach(markerItem => { // stationId便marker
this.stationList = [] //
markerList.forEach(markerItem => {
// stationId便marker
if (markerItem.stationId) {
//
markerItem.stationType = MarkerType.ImsRnStationG
stationList.push(markerItem)
this.stationList.push(markerItem)
} else {
//
markerItem.stationType = MarkerType.NuclearFacility
markerItem.lon = markerItem.longitude
markerItem.lat = markerItem.latitude
markerItem.stationId = markerItem.facilityId
}
})
@ -508,7 +527,7 @@ export default {
this.markerList = markerList
this.emitStationChange()
this.drawCircle(stationList)
this.drawCircle()
} else {
this.$message.error(message)
}
@ -520,13 +539,53 @@ export default {
},
//
drawCircle(stationList) {
this.circleLayer = new VectorLayer({
source: new VectorSource({
features: []
}),
properties: { name: 'eventMarker' }
drawCircle() {
const source = this.circleLayer.getSource()
source.clear()
const circleFeatures = []
this.stationList.forEach(stationItem => {
circleFeatures.push(this.getCircle(stationItem))
})
source.addFeatures(circleFeatures)
},
getCircle(stationInfo) {
const { lon, lat } = stationInfo
//
const fill = new Fill({
color: 'rgba(255, 0, 0, .4)' //
})
//
const stroke = new Stroke({
color: 'rgba(255, 0, 0, .4)', //
width: 1 //
})
//
const style = new Style({
fill: fill,
stroke: stroke
})
const circle = new Circle(fromLonLat([lon, lat]), this.getRadius())
const feature = new Feature({
geometry: circle,
style: style
})
feature.setStyle(style)
return feature
},
//
getRadius() {
const metersPerUnit = this.map
.getView()
.getProjection()
.getMetersPerUnit()
const circleRadius = (this.radius * 1000) / metersPerUnit
return circleRadius
},
//

View File

@ -7,8 +7,8 @@ import Ship from '@/assets/images/station-operation/ship.png'
export const MarkerType = {
Car: 'Car',
GroudMonitoringStation: 'Groud monitoring station',
ImsRnStationP: 'IMS STATION',
ImsRnStationG: 'IMS STATION',
ImsRnStationP: 'IMS STATION(P)',
ImsRnStationG: 'IMS STATION(G)',
NuclearFacility: 'Nuclear Facility',
Ship: 'Ship'

View File

@ -0,0 +1,157 @@
import { MarkerType } from './markerEnum'
// 核设施详情弹窗中的字段配置
export default {
[MarkerType.NuclearFacility]: [
{
label: 'ACTIVITY DAY',
key: 'activityDay'
},
{
label: 'ACTIVITY_YEAR',
key: 'activityYear'
},
{
label: 'BUILDDATE',
key: 'buildDate'
},
{
label: 'CAPACITYGROSS',
key: 'capacitygross'
},
{
label: 'CAPACITYNET',
key: 'capacitynet'
},
{
label: 'CAPACITYTHERMAL',
key: 'capacitythermal'
},
{
label: 'CRITICALITYDATE',
key: 'criticalityDate'
},
{
label: 'FACILITY ID',
key: 'facilityId'
},
{
label: 'FACILITY_NAME',
key: 'facilityName'
},
{
label: 'GRIDCONEETIONDATE',
key: 'gridconeetionDate'
},
{
label: 'LATITUDE',
key: 'lat'
},
{
label: 'LOCATION',
key: 'location'
},
{
label: 'LONGITUDE',
key: 'lon'
},
{
label: 'OPERARTOR',
key: 'operartor'
},
{
label: 'OWNER',
key: 'owner'
},
{
label: 'RETIREDATE',
key: 'retireDate'
},
{
label: 'STATUS',
key: 'status'
},
{
label: 'TYPE',
key: 'type'
},
{
label: 'VENDOR',
key: 'vendor'
},
],
[MarkerType.ImsRnStationP]: [{
label: 'COUNTRYCODE',
key: 'countryCode'
}, {
label: 'DATEBEGIN',
key: 'dateBegin'
}, {
label: 'DATEEND',
key: 'dateEnd'
}, {
label: 'DESCRIPTION',
key: 'description'
}, {
label: 'ELEVATION',
key: 'elevation'
}, {
label: 'LATITUDE',
key: 'lat'
}, {
label: 'LONGITUDE',
key: 'lon'
}, {
label: 'MODDATE',
key: 'moddate'
}, {
label: 'STATIONCODE',
key: 'stationCode'
}, {
label: 'STATIONID',
key: 'stationId'
}, {
label: 'STATUS',
key: 'status'
}, {
label: 'TYPE',
key: 'type'
}],
[MarkerType.ImsRnStationG]: [{
label: 'COUNTRYCODE',
key: 'countryCode'
}, {
label: 'DATEBEGIN',
key: 'dateBegin'
}, {
label: 'DATEEND',
key: 'dateEnd'
}, {
label: 'DESCRIPTION',
key: 'description'
}, {
label: 'ELEVATION',
key: 'elevation'
}, {
label: 'LATITUDE',
key: 'lat'
}, {
label: 'LONGITUDE',
key: 'lon'
}, {
label: 'MODDATE',
key: 'moddate'
}, {
label: 'STATIONCODE',
key: 'stationCode'
}, {
label: 'STATIONID',
key: 'stationId'
}, {
label: 'STATUS',
key: 'status'
}, {
label: 'TYPE',
key: 'type'
}]
}

View File

@ -86,7 +86,7 @@
ref="customScrollContainerRef"
class="scroller"
:items="dataList"
:item-size="108"
:item-size="129"
key-field="stationId"
v-slot="{ item }"
>
@ -139,8 +139,13 @@
ref="map"
token="AAPK2b935e8bbf564ef581ca3c6fcaa5f2a71ZH84cPqqFvyz3KplFRHP8HyAwJJkh6cnpcQ-qkWh5aiyDQsGJbsXglGx0QM2cPm"
>
<MapMarker :list="markerList" />
<MapPane :treeData="treeData" @changeMarker="onChangeMarker" @changeMarkerByType="onChangeMarkerByType" />
<MapMarker :list="markerList" @markerClick="onMarkerClick" />
<MapPane
ref="mapPane"
:treeData="treeData"
@changeMarker="onChangeMarker"
@changeMarkerByType="onChangeMarkerByType"
/>
</Map>
</div>
</div>
@ -332,6 +337,11 @@ export default {
this.markerList = this.originalDataList.filter(item => typeList.includes(item.stationType))
},
//
onMarkerClick() {
this.$refs.mapPane.handleOpenAnalyzeModal()
},
getScrollContainer() {
return this.$refs.customScrollContainerRef.$el
},