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

532 lines
14 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>
import { computed, onMounted, onUnmounted, watch } from 'vue'
import * as maptalks from 'maptalks'
import GlobalMap from './js/GlobalMap'
import useMapStore from '@/store/modules/map'
import { getAssetsFile } from '@/utils/common'
import * as BoatUtil from './lbtbox/boatTerminal'
import * as $configs from './map-config.js'
import { ElMessage } from 'element-plus'
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-25 21:30:27 +08:00
import { videoCameraFindPage, findUavPage, findEnvPage, dsVideoList } 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)
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)
drawSector('_monitor', [ item.longitude, item.latitude ], 5 * 1000, item.id, 30)
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}`
const circleGeometry = vector.sectors_monitor.getGeometryById(baseId + '_circle')
const ellipseGeometry = vector.sectors_monitor.getGeometryById(baseId + '_ellipse')
const lineGeometry = vector.sectors_monitor.getGeometryById(baseId + '_line')
if (circleGeometry) {
circleGeometry.show()
}
if (ellipseGeometry) {
ellipseGeometry.show()
}
if (lineGeometry) {
lineGeometry.show()
}
2025-12-24 18:19:05 +08:00
mapStore.updateWindowInfo({ visible: true, type: '_monitor', data: { ...item } })
})
}
})
vector.monitor.show()
}
/**
* 叠加无人机数据
* @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-26 00:19:22 +08:00
if(item.sourceType) {
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) => {
const sectorId = `sector_${type}${id}`
// 如果已存在同ID的扇形则先移除
const existingSector = vector['sectors' + type].getGeometryById(sectorId)
if (existingSector) {
vector['sectors' + type].removeGeometry(existingSector)
}
// 如果已有该监控点的扇形图层,则先移除
if (globalMap.map.getLayer(sectorId)) {
globalMap.map.getLayer(sectorId).remove()
}
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
}
})
if(angle) {
let ellipse = new maptalks.Sector(center, radius - 1 * 1000, angle - 30, angle + 30, {
2025-12-25 18:33:15 +08:00
id: sectorId + '_ellipse',
2025-12-24 18:19:05 +08:00
symbol: {
lineColor: '#FF8D1C',
polygonFill: '#ff8d1c29'
}
})
let line = new maptalks.Sector(center, radius + 1 * 1000, 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)
// vector['sectors' + type].hide()
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)
marker.on('click', (evt) => {
})
}
})
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)
marker.on('click', (evt) => {
})
}
})
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-25 21:30:27 +08:00
const params = {
pageNo: 1,
pageSize: 9999
}
videoCameraFindPage(params).then(res => {
if (res.success) {
geography.monitor = res.result.records
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 = () => {
mapStore.updateLargeModel(true)
}
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()
}
})
watch(() => monitor.value, () => {
if(monitor.value) {
initMonitor()
}else{
vector.monitor?.hide()
}
})
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
}
}
})
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>