You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

680 lines
22 KiB

1 month ago
<template>
<div>
<div class="parent">
<div class="mapParent">
<div :id="defaultData.mapName" class="my-map"></div>
</div>
<div class="child">
<div class="text-title">状态图示</div>
<div>
<div class="circle circle-green"></div>
<div class="text-info text-info-green">正在使用岸电</div>
</div>
<div>
<div class="circle circle-yellow"></div>
<div class="text-info text-info-yellow">船方原因未使用岸电</div>
</div>
<div>
<div class="circle circle-red"></div>
<div class="text-info text-info-red">岸方原因未使用岸电</div>
</div>
</div>
<!-- <div class="child" v-if="showButton && usingData.status === 2">-->
<!-- <el-button class="text-title" style="width:100%;background-color: rgba(255, 255, 255, 0);text-align: center;border: none;" @click="updateStatus">确认该事件</el-button>-->
<!-- </div>-->
</div>
</div>
</template>
<script setup lang="ts">
import interface_red from '@/assets/svgs/svg/岸电箱-维护保养.png'
import interface_yellow from '@/assets/svgs/svg/用电接口-黄色.gif'
import interface_blue from '@/assets/svgs/svg/岸电箱-正常.png'
import interface_green from '@/assets/svgs/svg/用电接口-绿色.svg'
import ship_red from '@/assets/svgs/svg/船-红色.gif'
import ship_yellow from '@/assets/svgs/svg/船-黄色.svg'
import ship_blue from '@/assets/svgs/svg/船-蓝色.svg'
import ship_green from '@/assets/svgs/svg/船-绿色.png'
import ship_brown from '@/assets/svgs/svg/船-棕色.svg'
import ship_problem from '@/assets/svgs/svg/船-问号.gif'
import shorepower_blue from '@/assets/svgs/svg/配电室.png'
import shorepower_green from '@/assets/svgs/svg/岸电设施-绿色.svg'
import problem from '@/assets/svgs/svg/问号-棕色.svg'
import problem_red from '@/assets/svgs/svg/问号-红色.svg'
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet-rotatedmarker";
import {MapApi} from "@/api/shorepower/map";
import baseInfo from "@/views/shorepower/map/baseMapInfo";
defineOptions({ name: 'PublicMapComponents' })
// const getShorePowerEquipmentStr = (icon: any) => {
// return `<div class="table-message">
// <div class="table-name">${icon.info.name}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'>名称 :${icon.info.name}</span></br>
// <span class='table-info'>容量:${icon.info.installedPower}</span></br>
// <span class='table-info'>电压 :${icon.info.voltage}</span></br>
// <span class='table-info'>频率:${icon.info.frequency}</span></br>
// <span class='table-info'>接口数量 :${icon.info.interfaceCount}</span></br>
// <span class='table-info'>同时可用数量:${icon.info.simultaneouslyInterfaceCount}</span></br>
// </div>
// </div>`
// }
// const getBerthStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.info.berthName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'>泊位 :${icon.info.berthName}</span></br>
// <span class='table-info'>总电量:${icon.info.totalPower}</span></br>
// <span class='table-info'>更新时间 :${icon.info.updateTime}</span></br>
// <span class='table-info'>状态:${icon.info.status}</span></br>
// <span class='table-info'>接口规格 :${icon.info.InterfaceType}</span></br>
// <span class='table-info'>泊位水深:${icon.info.berthDepth}</span></br>
// </div>
// </div>`
// }
// const getShipStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.info.shipName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'>船名 :${icon.info.shipName}</span></br>
// <span class='table-info'>呼号:${icon.info.callSign}</span></br>
// <span class="table-info">船公司:${icon.info.shipCompany}</span></br>
// <span class="table-info">联系电话:${icon.info.phone}</span></br>
// <span class='table-info'>长度 :${icon.info.length}</span></br>
// <span class='table-info'>宽度:${icon.info.width}</span></br>
// <span class='table-info'>吨位 :${icon.info.tonnage}</span></br>
// <span class='table-info'>类型:${icon.info.type}</span></br>
// <span class='table-info'>货物名称:${icon.info.CommodityName}</span></br>
// <span class='table-info'>货物吨数:${icon.info.cargoWeight}</span></br>
// ${icon.reason == 9 ? `<span class='table-info'>岸电接入时间:${icon.info.startUsingTime}</span></br>` : `<span class='table-info'>岸电未使用原因:${reason.value[icon.reason]}</span></br>`}
// <span class='table-info'>计划离泊时间:${icon.info.scheduledDepartureTime}</span></br>
// ${icon.reason == 9 ? `<span class='table-info'>岸电用量:${icon.info.shorePowerConsumption}</span></br>` : ''}
// <span class='table-info'>辅机总功率:${icon.info.totalAuxiliaryPower}</span></br>
// </div>
// </div>
// `
// }
// const getProblemStr = (icon: any) => {
// return `
// <div class="table-message">
// <div class="table-name">${icon.parent.info.shipName}</div>
// <div class="table-line"></div>
// <div class="base-table-info">
// <span class='table-info'>船名 :${icon.parent.info.shipName}</span></br>
// <span class='table-info'>岸电未使用原因:${reason.value[icon.parent.reason]}</span></br>
// <span class='table-info'>计划离泊时间:${icon.parent.info.scheduledDepartureTime}</span></br>
// </div>
// </div>
// `
// }
let mapNum = 1
const usingAlarm = ref([])
const removeAlarm = () => {
for (let ships of usingAlarm.value) {
for (const alarm of usingAlarm.value[ships]) {
alarm.remove()
}
usingAlarm.value[ships] = []
}
}
const buildAlarm = (icon:any, nowMapNum: number, radius: number = 1, timeOutTime: number = 50, color: string = Math.random() > 0.95 ? `rgba(255, 50, 50, 1)` :`rgba(50, 255, 20, 0.13)`) => {
setTimeout(() => {
if (nowMapNum !== mapNum) return
if (_map.value.getZoom() <= 15) {
const alarm = L.circle(icon.xy, {
radius: 50 * radius,
color: color,
stroke: false,
interactive: false,
}).addTo(_map.value);
if (!usingAlarm.value[icon.id]) usingAlarm.value[icon.id] = []
usingAlarm.value[icon.id].push(alarm)
}
if (radius > (6 * 18 / _map.value.getZoom())) {
for (let nowAlarm of usingAlarm.value[icon.id]) {
nowAlarm.remove()
}
usingAlarm.value[icon.id] = []
buildAlarm(icon, nowMapNum, 1, 50, color)
} else (
buildAlarm(icon, nowMapNum, radius + 1, timeOutTime + 2 ** radius / 2 , color)
)
}, timeOutTime)
}
const svgs = {
interface_red: interface_red,
interface_yellow: interface_yellow,
interface_blue: interface_blue,
interface_green: interface_green,
ship_red: ship_red,
ship_yellow: ship_yellow,
ship_blue: ship_blue,
ship_green: ship_green,
ship_brown: ship_brown,
ship_problem: ship_problem,
shorepower_blue: shorepower_blue,
shorepower_green: shorepower_green,
problem: problem,
problem_red: problem_red
}
// 计算图标尺寸【不同缩放比例下】
const countIconSize = (icon: any): [number, number] => {
let zoomRatio = 2 ** (20 - _map.value.getZoom())
return [icon.width * icon.ratio / zoomRatio, icon.height * icon.ratio / zoomRatio]
}
// 计算图标中心点【不同缩放比例下】
const countIconAnchor = (icon: any): [number, number] => {
let zoomRatio = 2 ** (21 - _map.value.getZoom())
return [icon.width * icon.ratio / zoomRatio, icon.height * icon.ratio / zoomRatio]
}
// 计算文本尺寸【不同缩放比例下】
const countFontSize = (text: any) => {
return text.fontSize / (1.7 ** (20 - _map.value.getZoom())) * 10
}
// 构建绘制options
const buildOptions = (data: any) => {
try {
if ((data.type && data.type === 'gon') || (data.type && data.type === 'line') || (!data.type && (type.value == 'gon' || type.value == 'line'))) return data
if ((data.type && data.type === 'icon') || (!data.type && type.value == 'icon')) {
return {
icon: L.icon({
iconAnchor: countIconAnchor(data),
iconSize: countIconSize(data),
iconUrl: svgs[data.icon]
}),
rotationAngle: data.rotationAngle,
rotationOrigin: "center center"
}
}
if ((data.type && data.type === 'text') || (!data.type && type.value == 'text')) {
const fontSize = countFontSize(data)
let textWidth = fontSize * data.name.length
for (let i = 0; i < data.name.length; i++) {
if ((data.name[i]) in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#']) textWidth = textWidth - 0.5 * fontSize
}
textWidth = textWidth * 1.2
const tagAnchor = data.tagAnchorType === 1 ? [0, 0] : data.tagAnchorType === 2 ? [textWidth, 0] : data.tagAnchorType === 3 ? [0, fontSize] : data.tagAnchorType === 4 ? [textWidth, fontSize] : [textWidth / 2, fontSize / 2]
const style = `
background-color:${data.background};border-radius: 5px;text-align: center;
color: ${data.color};
width: ${textWidth}px;
font-size: ${fontSize}px;
font-style: oblique;
font-weight: ${data.fontSize * 33};
cursor: grab;`
return {
icon: L.divIcon({
className: data.name,
iconAnchor: tagAnchor,
html: `<div style="${style}">${data.name}</div>`
})
}
}
} catch (e) {
console.log(e, data)
}
}
const _map = ref<any>({_zoom: 14,_animateToCenter: {lat: 0, lng: 0}})
const mapRef = ref(null);
const defaultData = ref({
defCenter: {lat: 38.8422480135733, lng: 118.4933866580094},
defZoom: 12,
mapName: 'map',
illustration: true
})
const center = ref(defaultData.value.defCenter)
const zoom = ref(defaultData.value.defZoom)
const mouse = ref(defaultData.value.defCenter)
const type = ref('gon') // 1: 绘制区域 2: 绘制线 3: 绘制图标 4: 绘制文本
// 【区域】绘制参数
const polygon = ref({
stroke: true,
color:`rgb(50, 50, 50)`,
weight:1,
opacity:1,
fill:true,
fillColor:`rgb(255, 255, 200)`,
fillOpacity:1,
interactive:false,
smoothFactor:1,
xy:[]
})
const usingPolygon = ref()
const drawingPolygon = () => {
if (usingPolygon.value) usingPolygon.value.remove()
usingPolygon.value = L.polygon(polygon.value.xy, buildOptions(polygon.value)).addTo(_map.value)
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
// 【线】绘制参数
const polyline = ref({
interactive: false,
color: `rgb(50, 50, 50)`, // 描边颜色
dashArray: [10, 5],
weight: 1,// 描边宽度
smoothFactor: 0,
xy: [],
})
const usingPolyline = ref()
const drawingPolyline = () => {
if (usingPolyline.value) usingPolyline.value.remove()
usingPolyline.value = L.polyline(polyline.value.xy, buildOptions(polyline.value)).addTo(_map.value);
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
// 【图标】绘制参数
const marker = ref({
rotationAngle: 0,
width: 1000,
height: 5200,
ratio: 1,
iconType: 'ship',
icon: 'ship_green',
anchor: 'left',
xy: [],
})
const usingMarker = ref()
const drawingMarker = () => {
if (usingMarker.value) usingMarker.value.remove()
if (marker.value.xy.length !== 2) return
usingMarker.value = L.marker(marker.value.xy, buildOptions(marker.value)).addTo(_map.value);
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
// 【文本】绘制参数
const textType = ref('ship')
const text = ref({
name: '浙能2',
xy: [38.94212905193746, 118.4492737054825],
// type: 0,
fontSize: 16,
tagAnchorType: 1,
background: 'rgba(255, 255, 255, 0.7)',
color: 'rgba(50, 50, 50, 1)',
anchor: 'left',
})
const usingText = ref()
const drawingText = () => {
if (usingText.value) usingText.value.remove()
if (text.value.xy.length !== 2) return
usingText.value = L.marker(text.value.xy, buildOptions(text.value)).addTo(_map.value)
usingPolygon.value.on('click', () => {
type.value = 'text'
})
}
const getDrawingInfo = () => {
if (type.value === 'gon') {
return {type: type.value, ...polygon.value}
} else if (type.value === 'line') {
return {type: type.value, ...polyline.value}
} else if (type.value === 'icon') {
return {type: type.value, ...marker.value}
} else if (type.value === 'text') {
if (textType.value !== 'ship') {
const { anchor, ...textInfo} = text.value
return {type: type.value, ...textInfo}
} else {
return {type: type.value, ...text.value}
}
}
}
const doCopy = async () => {
const str = JSON.stringify(getDrawingInfo())
try {
// 尝试使用现代API
await navigator.clipboard.writeText(str);
// console.log('复制成功!',str);
return
} catch (err) {
// 降级处理
const textArea = document.createElement("textarea");
textArea.value = str;
textArea.style.cssText = "position: fixed; top: -1000px; opacity: 0;";
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
// console.log('复制成功!',str);
} catch (err) {
alert('复制失败,请手动复制')
// console.log('复制失败,请手动复制');
}
document.body.removeChild(textArea);
}
}
const copyData = async () => {
await doCopy()
}
const addDrawingList = async (event) => {
if (event.keyCode === 32 || event.key === ' ') {
event.preventDefault();
if (type.value === 'gon') {
polygon.value.xy.push([mouse.value.lat, mouse.value.lng])
drawingPolygon()
} else if (type.value === 'line') {
polyline.value.xy.push([mouse.value.lat, mouse.value.lng])
drawingPolyline()
} else if (type.value === 'icon') {
marker.value.xy = [mouse.value.lat, mouse.value.lng]
drawingMarker()
} else if (type.value === 'text') {
text.value.xy = [mouse.value.lat, mouse.value.lng]
drawingText()
}
} else if (event.keyCode === 46 || event.key === 'Delete') {
event.preventDefault();
if (type.value === 'gon') {
polygon.value.xy.pop()
drawingPolygon()
} else if (type.value === 'line') {
polyline.value.xy.pop()
drawingPolyline()
} else if (type.value === 'icon') {
marker.value.xy = []
drawingMarker()
} else if (type.value === 'text') {
text.value.xy = []
drawingText()
}
} else if (event.keyCode === 13 || event.key === 'Enter') {
await copyData()
}
}
const doDrawing = () => {
if (type.value === 'gon') {
drawingPolygon()
} else if (type.value === 'line') {
drawingPolyline()
} else if (type.value === 'icon') {
drawingMarker()
} else if (type.value === 'text') {
drawingText()
}
}
const history = ref()
const usingHistory = ref([])
const doHistory = ref(false)
const removeHistory = () => {
if (usingHistory.value) {
for (const item of usingHistory.value) {
item.remove()
}
usingHistory.value = []
}
}
const drawingHistoryPolygon = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.polygon(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
polygon.value = data
type.value = 'gon'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryPolyline = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.polyline(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
polyline.value = data
type.value = 'line'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryMarker = (data: any) => {
if (data.iconType === 'ship') {
const nowMapNum = JSON.parse(JSON.stringify(mapNum))
buildAlarm(data, nowMapNum)
}
if (_map.value.getZoom() < 12) return
const usingInfo = L.marker(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
marker.value = data
type.value = 'icon'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryTest = (data: any) => {
if (_map.value.getZoom() < 12) return
const usingInfo = L.marker(data.xy, buildOptions(data)).addTo(_map.value)
usingInfo.on('click', async () => {
if (doHistory.value) await drawingHistory(doHistory.value)
doHistory.value = true
text.value = data
type.value = 'text'
usingInfo.remove()
doDrawing()
})
usingHistory.value.push(usingInfo)
}
const drawingHistoryInfo = (data: any) => {
if (data.type === 'gon') {
drawingHistoryPolygon(data)
} else if (data.type === 'line') {
drawingHistoryPolyline(data)
} else if (data.type === 'icon') {
drawingHistoryMarker(data)
} else if (data.type === 'text') {
drawingHistoryTest(data)
}
}
const drawingHistory = async (justOne: boolean = false) => {
// if (!history.value) {
// history.value = await MapApi.getAllData()
// }
const dataInfo = history.value ? history.value : await MapApi.getAllData()
if (justOne) {
drawingHistoryInfo(getDrawingInfo())
} else {
for (const item of dataInfo) {
if (!item.data) continue
const data = JSON.parse(item.data)
if (!data.xy) continue
drawingHistoryInfo(data)
}
}
}
const buildMap = () => {
_map.value = L.map(defaultData.value.mapName, {
center: defaultData.value.defCenter,
zoom: defaultData.value.defZoom,
zoomControl: false,
attributionControl: false,
})
L.tileLayer(
// 'http://150.138.79.224:8000/seamap/allmap/{z}/{y}/{x}.png',
// 'https://api.shipxy.com/h5s/api/3.5/sample?key=0cd1a6224aaa4b5ab1f3d0e79bb543cc&projection_type=wm&x={x}&y={y}&z={z}',
// 'https://m12.shipxy.com/tile.c?l=Na&m=o&x={x}&y={y}&z={z}',
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
// '',
{
maxZoom: 19,
minZoom: 5,
attribution: ''
}).addTo(_map.value);
// 设置地图的最大边界
const southWest = L.latLng(38.30071455572194, 117.07885023513526); // 西南角的坐标
const northEast = L.latLng( 40.32351403031131, 119.9161164758117); // 东北角的坐标
_map.value.setMaxBounds(L.latLngBounds(southWest, northEast));
// 监听鼠标位置坐标
_map.value.on('mousemove', (e) => {
mouse.value = e.latlng;
});
// 监听缩放级别变化
_map.value.on('zoomend', async () => {
zoom.value = _map.value.getZoom();
removeHistory()
removeAlarm()
mapNum++
await drawingHistory()
doDrawing()
})
// 监听当前中心点坐标变化
_map.value.on('moveend', () => {
center.value = _map.value.getCenter();
});
}
const buildBaseInfo = () => {
for (const item of baseInfo) {
if (item.xy) L.polygon(item.xy, item).addTo(_map.value)
}
}
const exportImage = async () => {
try {
alert('功能暂不可用!')
} catch (error) {
console.error('地图导出失败:', error)
alert('地图导出失败,请重试')
}
}
/** 初始化 **/
onMounted(() => {
buildMap()
buildBaseInfo()
window.addEventListener('keyup', addDrawingList)
drawingHistory()
})
</script>
<style >
.parent {
position: relative; /* 启用Grid布局 */
}
.child {
position: absolute;
top: 10px;
left: 20px;
z-index: 1;
width: 200px;
height: 150px;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid rgba(30, 30, 30, 0.4);
border-radius: 15px;
}
.text-title {
margin-top: 10px;
font-size: 16px;
color: rgba(20, 200, 255, 1);
text-align: center;
font-weight: bold;
line-height: 30px;
text-shadow: 0 0 5px rgba(0, 0, 0, 1), 0 0 6px rgba(20, 200, 255, 0.3), 0 0 7px rgba(20, 200, 255, 0.2), 0 0 8px rgba(20, 200, 255, 0.1);
}
.circle {
width: 10px;
height: 10px;
border-radius: 5px;
margin-left: 15px;
display: inline-block;
}
.circle-green {
background-color: rgba(50, 255, 100, 1);
}
.circle-yellow {
background-color: rgba(255, 150, 0, 1);
}
.circle-red {
background-color: rgba(255, 70, 70, 1);
}
.text-info {
margin-left: 8px;
margin-top: 8px;
display: inline-block;
}
.text-info-green {
color: rgba(50, 255, 100, 1);
}
.text-info-yellow {
color: rgba(255, 150, 0, 1);
}
.text-info-red {
color: rgba(255, 70, 70, 1);
}
.mapParent {
max-width: 100%;
max-height: 100%;
overflow: hidden;
}
.my-map {
height: 100vh;
background-color: #A3CCFF;
//transform: scale(1.4);
}
.table-message {
width: 300px;
margin-top: 10px;
}
.table-name {
margin-top: 25px;
font-size: 20px;
color: rgb(0, 200, 255);
}
.table-line {
min-height: 3px;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
background-color: rgb(0, 200, 255);
}
.base-table-info {
}
.table-info {
margin-top: 10px;
font-size: 16px;
line-height: 25px;
color: rgba(50, 50, 50, 1);
}
</style>