Files
erqi-web/src/components/Map/index.vue

580 lines
16 KiB
Vue
Raw Normal View History

2025-12-24 18:19:05 +08:00
<template>
<div id="map"></div>
<div class="map-mask"></div>
<!-- 四周边框 -->
<div class="map-border"></div>
<img class="map-bottom" src="@/assets/images/common/icon-bottom.png" alt="">
<img class="AI" src="@/assets/images/common/icon-AI.png" alt="" @click="toModel">
<div class="AI AI-text" @click="toModel"></div>
<!-- 信息弹窗 -->
<InfoWindowComponent/>
<!-- 渔船信息弹窗 -->
<TrawlerInfoWindowComponent/>
</template>
<script setup>
2025-12-31 15:23:29 +08:00
import { computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
2025-12-24 18:19:05 +08:00
import * as maptalks from 'maptalks'
import GlobalMap from './js/GlobalMap'
import useMapStore from '@/store/modules/map'
import * as BoatUtil from './lbtbox/boatTerminal'
import * as $configs from './map-config.js'
import { monitors, uavs, stations, environmentals, fences, detailFences } from './js/mock.js'
import InfoWindowComponent from '@/components/Map/window/index.vue'
import TrawlerInfoWindowComponent from '@/components/Map/window/trawler.vue'
2025-12-31 15:23:29 +08:00
import { dsVideoList, findUavPage, getVideoInfo } from '@/api/device.js'
2025-12-24 18:19:05 +08:00
const mapStore = useMapStore()
const UAV = computed(() => mapStore.legend.UAV)
const monitor = computed(() => mapStore.legend.monitor)
const origin_monitor = computed(() => mapStore.legend.origin_monitor)
const ais_station = computed(() => mapStore.legend.ais_station)
const environmental = computed(() => mapStore.legend.environmental)
const fence = computed(() => mapStore.legend.fence)
const sector = computed(() => mapStore.sector)
2025-12-31 15:23:29 +08:00
const locateData = computed(() => mapStore.locate.data)
2025-12-24 18:19:05 +08:00
let globalMap = null
let vector = {}
const geography = {
monitor: [], // 监控数据
origin_monitor: [], // 原始监控数据
UAV: [], // 无人机
ais_station: [], // ais基站
environmental: [], // 环境监测
fence: [] // 电子围栏
}
let sectorLayer = null
const initMap = () => {
const mapDom = document.getElementById('map')
globalMap = new GlobalMap(mapDom)//, { seamlessZoom: false })
}
const initLayerToMap = (type) => { // 地理图层注册
if (vector[type]) {
vector[type].remove()
vector[type] = null
}
vector[type] = new maptalks.VectorLayer(type).addTo(globalMap.map)
}
// 叠加电子围栏数据
const addFenceToMap = (type) => {
initLayerToMap(type)
geography[type].forEach((e) => {
if (e.positionInfo) {
const params = []
const symbol = [
{
lineColor: e.lineColor,
lineWidth: e.lineWidth - 0,
lineDasharray: [ 4, 4 ],
polygonFill: e.fillColor,
polygonOpacity: e.diaphaneity - 0,
textFill: 'black',
textHaloFill: 'white',
textHaloRadius: 2,
textName: e.warnAreaName,
textSize: {
stops: [
[ 10, 0 ],
[ 11, 12 ]
]
}
}
]
// 点位数据处理
const arr = e.positionInfo.split(';').map((pair) => {
const [ x, y ] = pair.split(',').map(Number)
return { x, y }
})
params.push(arr)
params.push({ symbol, zIndex: 1 })
const geometry = new maptalks.Polygon(...params)
vector[type].addGeometry(geometry)
}
})
}
/**
* 叠加监控数据
* @param type
* @param prop 监控类型
*/
const addMonitorToMap = () => {
initLayerToMap('monitor')
initLayerToMap('sectors_monitor')
geography.monitor.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_monitor', { ...item, name: item.videoName }),
properties: item,
2025-12-25 18:33:15 +08:00
zIndex: 2
2025-12-24 18:19:05 +08:00
}
)
marker.addTo(vector.monitor)
2025-12-31 15:23:29 +08:00
const visionDistance = item.visionDistance * 1000 || 5 * 1000
const pvalue = item.ptzcfg?.pValue || 0
drawSector('_monitor', [ item.longitude, item.latitude ], visionDistance, item.id, (90 - pvalue) % 360)
2025-12-24 18:19:05 +08:00
marker.on('click', (evt) => {
2025-12-25 18:33:15 +08:00
// 先隐藏所有扇形
changeSectorsInLayer('sectors_monitor', false)
// 确保扇形图层是可见的
if (!vector.sectors_monitor.isVisible()) {
vector.sectors_monitor.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__monitor${item.id}`
2025-12-31 15:23:29 +08:00
vector.sectors_monitor.getGeometryById(baseId + '_circle')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_ellipse')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_line')?.show()
2025-12-25 18:33:15 +08:00
2025-12-24 18:19:05 +08:00
mapStore.updateWindowInfo({ visible: true, type: '_monitor', data: { ...item } })
})
}
})
vector.monitor.show()
2025-12-31 15:23:29 +08:00
}
// 要素/视频定位
const locateMoitor = (data) => {
const name = 'locate-monitor'
let layer = vector[name]
if (!layer) {
initLayerToMap('locate-monitor')
layer = vector['locate-monitor']
} else {
layer.clear()
}
const monitors = vector.monitor
const marker = monitors.getGeometryById(data.id)
if (marker) {
const coordinates = marker.getCoordinates()
geography.monitor.forEach((item) => {
if (item.id === data.id) {
// globalMap.map.animateTo(
// {
// center: [ coordinates.x, coordinates.y ]
// },
// {
// duration: 1000 * 0.5,
// easing: 'out'
// }
// )
// 先隐藏所有扇形
changeSectorsInLayer('sectors_monitor', false)
getVideoInfo({ id: item.id }).then(res => {
const visionDistance = item.visionDistance * 1000 || 5 * 1000 // 视角距离
const pvalue = res.data.ptzcfg.pValue || 0 // 旋转角度
drawSector('_monitor', [ item.longitude, item.latitude ], visionDistance, item.id, (90 - pvalue) % 360)
// 确保扇形图层是可见的
if (!vector.sectors_monitor.isVisible()) {
vector.sectors_monitor.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__monitor${item.id}`
vector.sectors_monitor.getGeometryById(baseId + '_circle')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_ellipse')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_line')?.show()
})
}
})
}
2025-12-24 18:19:05 +08:00
}
/**
* 叠加无人机数据
* @param type
* @param prop 无人机类型
*/
const addUAVToMap = () => {
initLayerToMap('UAV')
initLayerToMap('sectors_UAV')
geography.UAV.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_UAV', { ...item, name: item.videoName }),
properties: item,
2025-12-25 18:33:15 +08:00
zIndex: 2
2025-12-24 18:19:05 +08:00
}
)
marker.addTo(vector.UAV)
drawSector('_UAV', [ item.longitude, item.latitude ], 5 * 1000, item.id)
marker.on('click', (evt) => {
2025-12-25 18:33:15 +08:00
// 先隐藏所有扇形
changeSectorsInLayer('sectors_UAV', false)
// 确保扇形图层是可见的
if (!vector.sectors_UAV.isVisible()) {
vector.sectors_UAV.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__UAV${item.id}`
const circleGeometry = vector.sectors_UAV.getGeometryById(baseId + '_circle')
if (circleGeometry) {
circleGeometry.show()
}
2025-12-31 15:23:29 +08:00
if(item.sourceType === '1' || item.sourceType === '2') {
2025-12-26 00:19:22 +08:00
mapStore.updateWindowInfo({ visible: true, type: '_UAV', data: { ...item } })
}
2025-12-24 18:19:05 +08:00
})
}
})
vector.UAV.show()
}
2025-12-25 18:33:15 +08:00
// 添加辅助函数来隐藏所有扇形
const changeSectorsInLayer = (layerName, show) => {
if (vector[layerName]) {
const allGeometries = vector[layerName].getGeometries()
allGeometries.forEach(geometry => {
if(show) {
geometry.show()
}else{
geometry.hide()
}
})
}
}
2025-12-24 18:19:05 +08:00
// 需要监控的起始角度和结束角度,修改视野范围可以传递经纬度坐标
const drawSector = (type, center, radius, id, angle) => {
2025-12-31 15:23:29 +08:00
const sectorLayerName = 'sectors' + type
// 检查是否存在扇形图层,不存在则初始化
if (!vector[sectorLayerName]) {
initLayerToMap(sectorLayerName.replace('sectors', 'sectors_')) // 例如 sectors_monitor -> sectors_monitor
}
2025-12-24 18:19:05 +08:00
const sectorId = `sector_${type}${id}`
2025-12-31 15:23:29 +08:00
2025-12-24 18:19:05 +08:00
// 如果已存在同ID的扇形则先移除
2025-12-31 15:23:29 +08:00
const existingCircle = vector[sectorLayerName].getGeometryById(sectorId + '_circle')
const existingEllipse = vector[sectorLayerName].getGeometryById(sectorId + '_ellipse')
const existingLine = vector[sectorLayerName].getGeometryById(sectorId + '_line')
2025-12-24 18:19:05 +08:00
2025-12-31 15:23:29 +08:00
if (existingCircle) {
vector[sectorLayerName].removeGeometry(existingCircle)
}
if (existingEllipse) {
vector[sectorLayerName].removeGeometry(existingEllipse)
}
if (existingLine) {
vector[sectorLayerName].removeGeometry(existingLine)
2025-12-24 18:19:05 +08:00
}
let circle = new maptalks.Circle(center, radius, {
2025-12-25 18:33:15 +08:00
id: sectorId + '_circle',
2025-12-24 18:19:05 +08:00
symbol: {
lineColor: '#1CA8FF',
lineWidth: 1,
lineOpacity: 1,
polygonFill: '#1ca8ff',
polygonOpacity: 0.16
}
})
2025-12-31 15:23:29 +08:00
if(typeof angle === 'number' && !isNaN(angle)) {
let ellipse = new maptalks.Sector(center, radius, angle - 10, angle + 10, {
2025-12-25 18:33:15 +08:00
id: sectorId + '_ellipse',
2025-12-24 18:19:05 +08:00
symbol: {
lineColor: '#FF8D1C',
polygonFill: '#ff8d1c29'
}
})
2025-12-31 15:23:29 +08:00
let line = new maptalks.Sector(center, radius, angle, angle, {
2025-12-25 18:33:15 +08:00
id: sectorId + '_line',
2025-12-24 18:19:05 +08:00
symbol: {
lineColor: '#FF8D1C',
polygonFill: '#ff8d1c29',
lineDasharray: [ 5, 10 ]
}
})
vector['sectors' + type].addGeometry([ circle, ellipse, line ])
}else{
vector['sectors' + type].addGeometry(circle)
}
// 添加到可视域图层
2025-12-25 18:33:15 +08:00
changeSectorsInLayer('sectors' + type, false)
2025-12-24 18:19:05 +08:00
}
/**
* 叠加ais基站数据
* @param type
* @param prop ais基站类型
*/
const addAisStationToMap = () => {
initLayerToMap('ais_station')
geography.ais_station.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_ais_station', { ...item }),
properties: item,
2025-12-25 18:33:15 +08:00
zIndex: 2
2025-12-24 18:19:05 +08:00
}
)
marker.addTo(vector.ais_station)
}
})
vector.ais_station.show()
}
/**
* 叠加环境监测数据
* @param type
* @param prop 环境监测类型
*/
const addEnvironmentalToMap = () => {
initLayerToMap('environmental')
geography.environmental.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_environmental', { ...item }),
properties: item,
2025-12-25 18:33:15 +08:00
zIndex: 2
2025-12-24 18:19:05 +08:00
}
)
marker.addTo(vector.environmental)
}
})
vector.environmental.show()
}
const initUAV = () => {
2025-12-25 21:30:27 +08:00
const params = {
pageNo: 1,
pageSize: 9999
}
findUavPage(params).then(res => {
if (res.success) {
geography.UAV = res.result.records
addUAVToMap()
}
})
2025-12-24 18:19:05 +08:00
}
const initMonitor = () => {
2025-12-31 15:23:29 +08:00
const params = {}
2025-12-26 01:14:12 +08:00
dsVideoList(params).then(res => {
2025-12-26 02:53:44 +08:00
geography.monitor = res.data
2025-12-24 18:19:05 +08:00
addMonitorToMap()
2025-12-25 21:30:27 +08:00
})
2025-12-24 18:19:05 +08:00
}
const initAisStation = () => {
geography.ais_station = stations
addAisStationToMap()
}
const initEnvironmental = () => {
geography.environmental = environmentals
addEnvironmentalToMap()
}
const initFence = () => {
geography.fence = fences
geography.detailFence = detailFences
addFenceToMap('fence')
addFenceToMap('detailFence')
}
const toModel = () => {
2025-12-31 15:23:29 +08:00
mapStore.updateDialog({ visible: true, type: 'largeModel' })
2025-12-24 18:19:05 +08:00
}
onMounted(() => {
initMap()
initFence()
initUAV()
initMonitor()
initAisStation()
initEnvironmental()
// 渔船链接
BoatUtil.init(mapStore)
BoatUtil.getShip()
// 轨迹图层
initLayerToMap('track')
})
watch(() => UAV.value, () => {
if(UAV.value) {
initUAV()
}else{
vector.UAV?.hide()
2025-12-31 15:23:29 +08:00
changeSectorsInLayer('sectors_UAV', false)
2025-12-24 18:19:05 +08:00
}
})
watch(() => monitor.value, () => {
if(monitor.value) {
initMonitor()
}else{
vector.monitor?.hide()
2025-12-31 15:23:29 +08:00
changeSectorsInLayer('sectors_monitor', false)
2025-12-24 18:19:05 +08:00
}
})
watch(() => ais_station.value, () => {
if(ais_station.value) {
initAisStation()
}else{
vector.ais_station?.hide()
}
})
watch(() => environmental.value, () => {
if(environmental.value) {
initEnvironmental()
}else{
vector.environmental?.hide()
}
})
watch(() => fence.value, () => {
if(fence.value) {
initFence()
}else{
vector.fence?.hide()
vector.detailFence?.hide()
}
})
watch(() => sector.value.monitor, (newVal) => {
if(vector.sectors_monitor) {
if(newVal) {
2025-12-25 18:33:15 +08:00
// vector.sectors_monitor.show()
changeSectorsInLayer('sectors_monitor', true)
2025-12-24 18:19:05 +08:00
} else {
2025-12-25 18:33:15 +08:00
// vector.sectors_monitor.hide()
changeSectorsInLayer('sectors_monitor', false)
2025-12-24 18:19:05 +08:00
}
}
})
watch(() => sector.value.UAV, (newVal) => {
if(vector.sectors_UAV) {
if(newVal) {
2025-12-25 18:33:15 +08:00
// vector.sectors_UAV.show()
changeSectorsInLayer('sectors_UAV', true)
2025-12-24 18:19:05 +08:00
} else {
2025-12-25 18:33:15 +08:00
// vector.sectors_UAV.hide()
changeSectorsInLayer('sectors_UAV', false)
2025-12-24 18:19:05 +08:00
}
}
})
2025-12-31 15:23:29 +08:00
// 定位数据变化
watch(() => locateData.value.videoCode, (newVal) => {
nextTick(() => {
locateMoitor(locateData.value)
})
})
2025-12-24 18:19:05 +08:00
onUnmounted(() => {
globalMap.destroy()
globalMap = null
BoatUtil.destroyWebsocket()
})
</script>
<style lang="scss" scoped>
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.map-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('@/assets/images/common/map-mask.png');
background-size: 100% 100%;
background-repeat: no-repeat;
pointer-events: none;
}
.map-border {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
height: calc(100% - 18px);
background-image: url('@/assets/images/common/map-border.png');
background-size: 100% 100%;
background-repeat: no-repeat;
pointer-events: none;
}
.map-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%);
}
.AI{
position: absolute;
bottom: 15px;
left: 50%;
transform: translate(-50%);
cursor: pointer;
z-index: 2;
}
.AI-text{
font-family: 'YouSheBiaoTiHei';
font-size: 22px;
-webkit-text-stroke: 1px rgba(4,176,244,0.4);
text-align: left;
font-style: normal;
text-transform: none;
background-image: linear-gradient(180deg, #FFFFFF 5%, #2DE5FF 100%);
-webkit-background-clip: text;
bottom: 33px;
-webkit-text-fill-color: transparent;
}
// 弹窗样式
:deep(.dialog-wrapper.el-dialog){
// height: 900px;
background: linear-gradient(90deg, rgba(12, 25, 41, 0.8) 0%, rgba(12, 25, 41, 0.6) 100%);
border: 1px solid #00C0FF;
border-radius: 0;
padding: 0;
.el-dialog__header{
2025-12-25 18:33:15 +08:00
display: flex;
justify-content: space-between;
2025-12-24 18:19:05 +08:00
padding: 0 16px;
background: linear-gradient(0deg, rgba(10, 169, 255, 0) 0%, rgba(10, 169, 255, 0.5) 100%);
.title{
display: flex;
justify-content: space-between;
align-items: center;
font-size: 20px;
color: #FFFFFF;
text-align: left;
height: 56px;
.icon-group img {
cursor: pointer;
&:active {
opacity: .8;
}
}
}
}
.el-dialog__body {
padding: 16px;
box-sizing: border-box;
}
}
</style>