|
@@ -1,5 +1,11 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div id="tracking-map" style="width: 100%; height: 448px" class="tracking-map"></div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ id="tracking-map"
|
|
|
|
|
+ ref="mapContainer"
|
|
|
|
|
+ style="width: 100%; height: 448px"
|
|
|
|
|
+ class="tracking-map"
|
|
|
|
|
+ :class="{ 'dark-mode': themeStore.theme === 'dark' }"
|
|
|
|
|
+ ></div>
|
|
|
</template>
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import L from 'leaflet'
|
|
import L from 'leaflet'
|
|
@@ -7,6 +13,9 @@ import DestinationIcon from '../images/destinationIcon.png'
|
|
|
import OriginIcon from '../images/originIcon.png'
|
|
import OriginIcon from '../images/originIcon.png'
|
|
|
import TransferIcon from '../images/transferIcon.png'
|
|
import TransferIcon from '../images/transferIcon.png'
|
|
|
import { onMounted, ref, watch } from 'vue'
|
|
import { onMounted, ref, watch } from 'vue'
|
|
|
|
|
+import { useThemeStore } from '@/stores/modules/theme'
|
|
|
|
|
+
|
|
|
|
|
+const themeStore = useThemeStore()
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
|
serial_no?: string
|
|
serial_no?: string
|
|
@@ -89,7 +98,6 @@ let initialZoomLevel: number | null = null
|
|
|
let isFirstRender = true // 标记是否为首次渲染
|
|
let isFirstRender = true // 标记是否为首次渲染
|
|
|
|
|
|
|
|
let allMarkers = []
|
|
let allMarkers = []
|
|
|
-let visibleMarkers = new Set()
|
|
|
|
|
|
|
|
|
|
// 添加标记后更新中心和缩放级别
|
|
// 添加标记后更新中心和缩放级别
|
|
|
const addMarkersToMap = () => {
|
|
const addMarkersToMap = () => {
|
|
@@ -118,166 +126,19 @@ const addMarkersToMap = () => {
|
|
|
allMarkers[`${position.lat},${position.lng}`] = marker
|
|
allMarkers[`${position.lat},${position.lng}`] = marker
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+<<<<<<< HEAD
|
|
|
updateVisibleMarkers()
|
|
updateVisibleMarkers()
|
|
|
|
|
|
|
|
if (viewData.value.length > 0) {
|
|
if (viewData.value.length > 0) {
|
|
|
- // 根据标记的位置设置中心点以及缩放级别
|
|
|
|
|
- const bounds = L.latLngBounds(viewData.value)
|
|
|
|
|
- map!.fitBounds(bounds, { paddingTopLeft: [20, 70], paddingBottomRight: [400, 0] })
|
|
|
|
|
|
|
+>>>>>>> dev
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
if (isFirstRender) {
|
|
if (isFirstRender) {
|
|
|
initialCenter = map!.getCenter()
|
|
initialCenter = map!.getCenter()
|
|
|
- initialZoomLevel = map!.getZoom()
|
|
|
|
|
isFirstRender = false
|
|
isFirstRender = false
|
|
|
}
|
|
}
|
|
|
addResetZoomButton(initialCenter!, initialZoomLevel!)
|
|
addResetZoomButton(initialCenter!, initialZoomLevel!)
|
|
|
- }, 0)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 新增轮船当前位置标记
|
|
|
|
|
-const addShipMarker = (x: number) => {
|
|
|
|
|
- const solidLine = allMapData.value.solidLine
|
|
|
|
|
- // 如果轮船还未出发,则显示起点轮船标记
|
|
|
|
|
- if (solidLine.length === 0) {
|
|
|
|
|
- // 创建轮船图标
|
|
|
|
|
- const arrowIcon = L.divIcon({
|
|
|
|
|
- html: `
|
|
|
|
|
- <div class="container">
|
|
|
|
|
- <div class="circle"></div>
|
|
|
|
|
- <span style="padding: 0; color:white; border:1px solid white" class="font_family icon-icon_ocean_b"></span>
|
|
|
|
|
- </div>
|
|
|
|
|
- `,
|
|
|
|
|
- className: 'arrow-icon',
|
|
|
|
|
- iconSize: [50, 50],
|
|
|
|
|
- iconAnchor: [25, 25], // 箭头的中心点
|
|
|
|
|
- popupAnchor: [0, -25] // 弹出框的锚点
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- let curMarkerLocation = markerPositions.value.find((item) => item.label === 'Origin')
|
|
|
|
|
- const arrowMarker = L.marker([curMarkerLocation.lat, curMarkerLocation.lng + x * 360], {
|
|
|
|
|
- icon: arrowIcon
|
|
|
|
|
- }).addTo(map)
|
|
|
|
|
- track_added_marker.push(arrowMarker)
|
|
|
|
|
- } else if (solidLine.length > 0) {
|
|
|
|
|
- // 如果轮船已经出发,则显示轮船当前位置标记
|
|
|
|
|
- // 如果线段至少有两个点,才添加箭头
|
|
|
|
|
- // 获取线段的最后一个点和倒数第二个点
|
|
|
|
|
- const lastPoint = solidLine[solidLine.length - 1]
|
|
|
|
|
- const secondLastPoint = solidLine[solidLine.length - 2]
|
|
|
|
|
- console.log(lastPoint, secondLastPoint, 'lastPoint, secondLastPoint')
|
|
|
|
|
- // 计算线段末端的角度(以弧度为单位)
|
|
|
|
|
- const angle =
|
|
|
|
|
- (Math.atan2(
|
|
|
|
|
- Number(lastPoint.lon) - Number(secondLastPoint.lon), // Δlon (x)
|
|
|
|
|
- Number(lastPoint.lat) - Number(secondLastPoint.lat) // Δlat (y)
|
|
|
|
|
- ) *
|
|
|
|
|
- (180 / Math.PI) +
|
|
|
|
|
- 360) %
|
|
|
|
|
- 360
|
|
|
|
|
- // 创建自定义箭头图标
|
|
|
|
|
- const arrowIcon = L.divIcon({
|
|
|
|
|
- html: `
|
|
|
|
|
- <div style="transform: rotate(${angle}deg);" class="container">
|
|
|
|
|
- <div class="circle"></div>
|
|
|
|
|
- <span style="color:white;border:1px solid white" class="font_family icon-icon_arrow_b"></span>
|
|
|
|
|
- </div>
|
|
|
|
|
- `,
|
|
|
|
|
- className: 'arrow-icon',
|
|
|
|
|
- iconSize: [50, 50],
|
|
|
|
|
- iconAnchor: [25, 25], // 箭头的中心点
|
|
|
|
|
- popupAnchor: [0, -25] // 弹出框的锚点
|
|
|
|
|
- })
|
|
|
|
|
- // 创建箭头标记,并根据计算出的角度旋转箭头
|
|
|
|
|
- const arrowMarker = L.marker([Number(lastPoint.lat), Number(lastPoint.lon) + x * 360], {
|
|
|
|
|
- icon: arrowIcon
|
|
|
|
|
- }).addTo(map)
|
|
|
|
|
- // 将箭头标记也存储在 track_added_marker 数组中,以便后续管理
|
|
|
|
|
- track_added_marker.push(arrowMarker)
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 更新可见标记
|
|
|
|
|
-const updateVisibleMarkers = () => {
|
|
|
|
|
- const newVisibleMarkers = new Set()
|
|
|
|
|
-
|
|
|
|
|
- let bnd = map.getBounds()
|
|
|
|
|
- let ww = bnd.getEast() - bnd.getWest()
|
|
|
|
|
-
|
|
|
|
|
- let cc = Math.ceil(ww / 360)
|
|
|
|
|
-
|
|
|
|
|
- let cc1 = Math.floor(bnd.getWest() / 360)
|
|
|
|
|
- let cc2 = Math.ceil(bnd.getEast() / 360)
|
|
|
|
|
- cc = cc2 - cc1 + 1
|
|
|
|
|
-
|
|
|
|
|
- let x = cc1
|
|
|
|
|
-
|
|
|
|
|
- // 移除不再可见的标记
|
|
|
|
|
- visibleMarkers.forEach((marker: any) => {
|
|
|
|
|
- if (!newVisibleMarkers.has(marker)) {
|
|
|
|
|
- map.removeLayer(marker)
|
|
|
|
|
- delete allMarkers[`${marker.getLatLng().lat},${marker._lng},${marker._x}`]
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // 计算当前视图中的标记,包括多地球的情况
|
|
|
|
|
- while (x >= cc1 && x <= cc2 && cc > 0) {
|
|
|
|
|
- Object.values(allMarkers).forEach((marker) => {
|
|
|
|
|
- const latLng = marker.getLatLng()
|
|
|
|
|
- const key = `${latLng.lat},${latLng.lng},${x}`
|
|
|
|
|
- if (!allMarkers[key]) {
|
|
|
|
|
- const newMarker: any = L.marker([latLng.lat, latLng.lng + x * 360], {
|
|
|
|
|
- icon: marker.options.icon
|
|
|
|
|
- })
|
|
|
|
|
- .bindPopup(marker.getPopup().getContent(), marker.getPopup().options)
|
|
|
|
|
- .openPopup()
|
|
|
|
|
- newMarker._x = x
|
|
|
|
|
- // 使用原始的经度作为标记的唯一标识
|
|
|
|
|
- newMarker._lng = latLng.lng
|
|
|
|
|
- allMarkers[key] = newMarker
|
|
|
|
|
- map.addLayer(newMarker)
|
|
|
|
|
- }
|
|
|
|
|
- newVisibleMarkers.add(allMarkers[key])
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- addShipMarker(x)
|
|
|
|
|
-
|
|
|
|
|
- x++
|
|
|
|
|
- cc--
|
|
|
|
|
|
|
+ }, 500)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 更新可见标记集合
|
|
|
|
|
- visibleMarkers = newVisibleMarkers
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 处理得到的数据
|
|
|
|
|
-const handleData = (data) => {
|
|
|
|
|
- let key = 0
|
|
|
|
|
- let curLine = []
|
|
|
|
|
- let resultLine = []
|
|
|
|
|
- data.forEach((item, index) => {
|
|
|
|
|
- if (item.sn === '1' && key === 0) {
|
|
|
|
|
- key++
|
|
|
|
|
- curLine.push([Number(item.lat), Number(item.lon)])
|
|
|
|
|
- } else if (item.sn === '1' && key !== 0) {
|
|
|
|
|
- resultLine.push({
|
|
|
|
|
- name: key,
|
|
|
|
|
- pts: curLine
|
|
|
|
|
- })
|
|
|
|
|
- curLine = [[Number(item.lat), Number(item.lon)]]
|
|
|
|
|
- key++
|
|
|
|
|
- }
|
|
|
|
|
- if (item.sn !== '1') {
|
|
|
|
|
- curLine.push([Number(item.lat), Number(item.lon)])
|
|
|
|
|
- }
|
|
|
|
|
- if (index === data.length - 1 && item.sn !== '1') {
|
|
|
|
|
- resultLine.push({
|
|
|
|
|
- name: key,
|
|
|
|
|
- pts: curLine
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- return resultLine
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const allMapData = ref()
|
|
const allMapData = ref()
|
|
@@ -301,20 +162,17 @@ const getMarker = () => {
|
|
|
Transfer: { color: '#ed0000', icon: transferIcon }
|
|
Transfer: { color: '#ed0000', icon: transferIcon }
|
|
|
}
|
|
}
|
|
|
markerPositions.value.push({
|
|
markerPositions.value.push({
|
|
|
- lat: item.lat,
|
|
|
|
|
- lng: (Number(item.lng) + 360) % 360,
|
|
|
|
|
|
|
+ lat: Number(item.lat),
|
|
|
|
|
+ lng: Number(item.lng),
|
|
|
city: item.infor,
|
|
city: item.infor,
|
|
|
label: item.label,
|
|
label: item.label,
|
|
|
icon: iconColorList[item.label].icon,
|
|
icon: iconColorList[item.label].icon,
|
|
|
iconColor: iconColorList[item.label].color
|
|
iconColor: iconColorList[item.label].color
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
- viewData.value = (data?.rangePoint.length > 0 ? data?.rangePoint : data?.point)?.map(
|
|
|
|
|
- (item) => {
|
|
|
|
|
- return [Number(item.lat), (Number(item.lon || item.lng) + 360) % 360]
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- // 请求成功后添加标记,并动态添加重置按钮
|
|
|
|
|
|
|
+ viewData.value = data?.map((item) => {
|
|
|
|
|
+ return [Number(item.lat), Number(item.lng)]
|
|
|
|
|
+ }) // 请求成功后添加标记,并动态添加重置按钮
|
|
|
addMarkersToMap()
|
|
addMarkersToMap()
|
|
|
if (data?.dottedLine) {
|
|
if (data?.dottedLine) {
|
|
|
draw_marker(handleData(data.dottedLine), handleData(data.solidLine))
|
|
draw_marker(handleData(data.dottedLine), handleData(data.solidLine))
|
|
@@ -405,6 +263,12 @@ onUnmounted(() => {
|
|
|
height: 26px;
|
|
height: 26px;
|
|
|
font-size: 18px;
|
|
font-size: 18px;
|
|
|
}
|
|
}
|
|
|
|
|
+ a.leaflet-control-zoom-in,
|
|
|
|
|
+ a.leaflet-control-zoom-out {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
.dark-mode {
|
|
.dark-mode {
|
|
|
div.leaflet-control-zoom.leaflet-bar.leaflet-control {
|
|
div.leaflet-control-zoom.leaflet-bar.leaflet-control {
|
|
@@ -412,17 +276,31 @@ onUnmounted(() => {
|
|
|
border-radius: 4px;
|
|
border-radius: 4px;
|
|
|
box-shadow: none;
|
|
box-shadow: none;
|
|
|
a {
|
|
a {
|
|
|
- background-color: #3c414a;
|
|
|
|
|
|
|
+ background-color: #30353c;
|
|
|
border-bottom: none;
|
|
border-bottom: none;
|
|
|
span {
|
|
span {
|
|
|
color: var(--color-neutral-1);
|
|
color: var(--color-neutral-1);
|
|
|
}
|
|
}
|
|
|
&:first-child {
|
|
&:first-child {
|
|
|
span {
|
|
span {
|
|
|
- display: inline-block;
|
|
|
|
|
- width: 24px;
|
|
|
|
|
- border-bottom: 2px solid #575c64;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ border-bottom: 1px solid #3f434a;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .reset-zoom-control {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ background-color: #30353c;
|
|
|
|
|
+ }
|
|
|
|
|
+ a.leaflet-bar-part {
|
|
|
|
|
+ background-color: #30353c;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ div {
|
|
|
|
|
+ border-color: var(--color-neutral-1) !important;
|
|
|
|
|
+ div {
|
|
|
|
|
+ background-color: var(--color-neutral-1) !important;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -472,6 +350,35 @@ onUnmounted(() => {
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* 示例:将所有地图图片的颜色反转 */
|
|
|
|
|
+.dark-mode img:not(.leaflet-marker-icon) {
|
|
|
|
|
+ filter: invert(1) hue-rotate(230deg) saturate(60%) brightness(60%) opacity(80%);
|
|
|
|
|
+}
|
|
|
|
|
+// 防止暗黑模式下地图超出容器
|
|
|
|
|
+.tracking-map {
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+// 修改暗黑模式下的背景色
|
|
|
|
|
+.leaflet-container.dark-mode,
|
|
|
|
|
+.leaflet-map-pane.dark-mode,
|
|
|
|
|
+.leaflet-tile-container.dark-mode {
|
|
|
|
|
+ background-color: #2b2f36;
|
|
|
|
|
+}
|
|
|
|
|
+// 处理版权信息在切换模式后样式错误bug
|
|
|
|
|
+.leaflet-right .leaflet-control-attribution {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.8);
|
|
|
|
|
+ margin: 0 4px 4px 0;
|
|
|
|
|
+ color: #2b2f36;
|
|
|
|
|
+ span {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #2b2f36;
|
|
|
|
|
+ }
|
|
|
|
|
+ a {
|
|
|
|
|
+ color: #0078a8;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
/* 自定义重置缩放按钮控件样式 */
|
|
/* 自定义重置缩放按钮控件样式 */
|
|
|
.reset-zoom-control {
|
|
.reset-zoom-control {
|
|
|
margin-top: 10px;
|
|
margin-top: 10px;
|