地图组件更新

This commit is contained in:
wangxk 2025-04-24 09:27:30 +08:00
parent 86999cc440
commit dba5d7ca8b
7 changed files with 143 additions and 275 deletions

View File

@ -49,6 +49,7 @@
"js-cookie": "^3.0.1",
"jsencrypt": "^3.3.2",
"jspdf": "^2.5.1",
"leaflet": "^1.9.4",
"marked": "^4.0.17",
"minimatch": "^5.1.0",
"monaco-editor": "^0.36.1",
@ -78,6 +79,7 @@
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@types/leaflet": "^1.9.17",
"@types/node": "^16.11.7",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",

View File

@ -167,6 +167,14 @@ export function startSimpleNavi(params:any){
params:params
})
}
//关闭
export function stopSimpleNavi(params:any){
return request ({
url:'/experimentalData/ts-files/stopSimpleNavi',
method:'post',
params:params
})
}
//读取text文件
export function apicontent(params:any){
return request ({

View File

@ -1,282 +1,137 @@
<template>
<div ref="mapContainer" class="map-container" :class="{ 'fullscreen': isFullscreen }">
<button class="fullscreen-btn" @click="toggleFullscreen">
{{ isFullscreen ? '退出全屏' : '全屏' }}
</button>
</div>
<!-- 地图容器 -->
<div id="map" style="height: 800px; width: 100%"></div>
</template>
<script setup>
import { ref, onMounted, watch, defineEmits, onBeforeUnmount } from 'vue'
import * as d3 from 'd3'
import chinaData from './newJson.json'
// ==================== ====================
const DEFAULT_MAP_SIZE = { width: 800, height: 600 }
const PROJECTION_CENTER = [104, 37]
const SCALE_FACTORS = { normal: 600, fullscreen: 1000 }
<script setup lang="ts">
import { onMounted, watch, nextTick, onUnmounted } from 'vue'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
// ==================== Props/Emits ====================
const props = defineProps({
coordinates: {
type: Array,
default: () => [
[116.405285, 39.904989],
[121.472644, 31.231706]
],
validator: value => value.every(coord =>
Array.isArray(coord) && coord.length === 2 &&
typeof coord[0] === 'number' && typeof coord[1] === 'number'
)
},
qvehuan: {
type: Boolean,
default: false
}
//
interface PointProp {
points?: Array<[number, number]>;
}
//
const MAP_CONFIG = {
center: [39.9042, 116.4074] as [number, number], //
zoom: 4, //
minZoom: 0, //
maxZoom: 18, //
zoomControl: true //
}
// 线
const POLYLINE_STYLE = {
color: 'blue', // 线
weight: 3, // 线
lineJoin: 'round' as const //
}
// props
const props = withDefaults(defineProps<PointProp>(), {
points: () => []
})
const emit = defineEmits(['qvehuan1'])
//
let map: L.Map | null = null
// 线
let polyline: L.Polyline | null = null
//
let markers: L.Marker[] = []
// ==================== ====================
const mapContainer = ref(null)
const isFullscreen = ref(false)
const isRouteInitialized = ref(false)
// ==================== D3 ====================
let svgInstance = null
let mapGroup = null
let markersGroup = null
let zoomBehavior = null
// ==================== ====================
const createProjection = () => {
const { width, height } = getContainerSize()
const projection = d3.geoMercator()
if (props.coordinates?.length >= 2) {
const validCoords = props.coordinates.filter(c => isValidCoordinate(c))
if (validCoords.length >= 2) {
const routeFeature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: validCoords
}
}
projection.fitExtent(
[[50, 50], [width - 50, height - 50]], //
routeFeature
)
return projection
}
}
return projection
.center(PROJECTION_CENTER)
.scale(isFullscreen.value ? SCALE_FACTORS.fullscreen : SCALE_FACTORS.normal)
.translate([width / 2, height / 2])
/**
* 初始化地图实例
*/
function initMap() {
map = L.map('map', MAP_CONFIG)
addTileLayer()
}
const initializeMap = () => {
if (svgInstance) svgInstance.remove()
const { width, height } = getContainerSize()
const projection = createProjection()
const pathGenerator = d3.geoPath().projection(projection)
svgInstance = d3.select(mapContainer.value)
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', '#f0f8ff')
const mainGroup = svgInstance.append('g')
mapGroup = mainGroup.append('g').attr('class', 'map-layer')
markersGroup = mainGroup.append('g').attr('class', 'markers-layer')
renderChinaMap(pathGenerator)
initializeZoomBehavior(mainGroup)
/**
* 添加离线瓦片图层
*/
function addTileLayer() {
L.tileLayer('/tiles/{z}/{y}/{x}.jpg', {
attribution: 'Offline Map', //
tileSize: 256, //
noWrap: true //
}).addTo(map!)
}
const renderChinaMap = (path) => {
mapGroup.append('g')
.selectAll('path')
.data(chinaData.features)
.enter()
.append('path')
.attr('d', path)
.attr('fill', '#e7e7e7')
.attr('stroke', '#fff')
.attr('stroke-width', 0.3)
mapGroup.append('g')
.selectAll('text')
.data(chinaData.features)
.enter()
.append('text')
.attr('transform', d => `translate(${path.centroid(d)})`)
.text(d => d.properties.name || '')
.attr('text-anchor', 'middle')
.style('font', '12px Arial')
.attr('fill', '#333')
.style('pointer-events', 'none')
/**
* 清除所有轨迹元素
*/
function clearLayers() {
// 线
polyline?.remove()
//
markers.forEach(marker => marker.remove())
markers = []
}
// ==================== 线 ====================
const updateRoute = () => {
if (!props.coordinates?.length) return
/**
* 将坐标转换为Leaflet的LatLng对象
* @param point 原始坐标数组
* @returns Leaflet坐标对象
*/
const convertToLatLng = (point: [number, number]) => L.latLng(point[0], point[1])
/**
* 绘制轨迹线
* @param latlngs 坐标点数组
*/
function createPolyline(latlngs: L.LatLng[]) {
polyline = L.polyline(latlngs, POLYLINE_STYLE).addTo(map!)
//
// map?.fitBounds(polyline.getBounds())
}
/**
* 创建标记点
* @param latlngs 坐标点数组
* @returns 标记实例数组
*/
function createMarkers(latlngs: L.LatLng[]) {
return latlngs.map(latlng =>
L.marker(latlng).addTo(map!)
)
}
/**
* 主绘制函数
* @param points 原始坐标数组
*/
function drawTrajectory(points: Array<[number, number]>) {
if (!map) return
initializeMap()
//
clearLayers()
const projection = createProjection()
const pathGenerator = d3.geoPath().projection(projection)
if (isRouteInitialized.value) {
mapGroup.selectAll('.route-path').remove()
markersGroup.selectAll('*').remove()
isRouteInitialized.value = false
emit('qvehuan1', isRouteInitialized.value)
//
if (points?.length > 0) {
const latlngs = points.map(convertToLatLng)
createPolyline(latlngs)
markers = createMarkers(latlngs)
}
renderRoutePath(pathGenerator)
renderMarkers(projection)
}
const renderRoutePath = (path) => {
const routeData = {
type: "LineString",
coordinates: props.coordinates
}
mapGroup.append('path')
.datum(routeData)
.attr('class', 'route-path')
.attr('d', path)
.attr('fill', 'none')
.attr('stroke', '#ff4757')
.attr('stroke-width', 2)
.attr('stroke-linecap', 'round')
}
const renderMarkers = (projection) => {
props.coordinates.forEach((coord, index) => {
if (!isValidCoordinate(coord)) return
const [x, y] = projection(coord)
//
markersGroup.append('circle')
.attr('cx', x)
.attr('cy', y)
.attr('r', 4) //
.attr('fill', '#ff4757')
.attr('stroke', 'white')
.attr('stroke-width', 1)
.raise() //
})
}
// ==================== ====================
const getContainerSize = () => ({
width: mapContainer.value?.clientWidth || DEFAULT_MAP_SIZE.width,
height: mapContainer.value?.clientHeight || DEFAULT_MAP_SIZE.height
})
const isValidCoordinate = (coord) =>
Array.isArray(coord) && coord.length === 2 &&
!isNaN(coord[0]) && !isNaN(coord[1])
// ==================== ====================
const initializeZoomBehavior = (group) => {
zoomBehavior = d3.zoom()
.scaleExtent([0.2, 68])
.on('zoom', ({ transform }) => {
group.attr('transform', transform)
})
svgInstance.call(zoomBehavior)
}
const handleWindowResize = () => {
initializeMap()
updateRoute()
}
const toggleFullscreen = () => {
isFullscreen.value = !isFullscreen.value
setTimeout(() => {
initializeMap()
updateRoute()
}, 100)
}
// ==================== ====================
//
onMounted(() => {
window.addEventListener('resize', handleWindowResize)
initializeMap()
if (props.coordinates?.length) {
updateRoute()
}
nextTick(() => {
initMap()
})
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleWindowResize)
//
onUnmounted(() => {
map?.remove()
map = null
})
watch(() => props.qvehuan, (newVal) => {
isRouteInitialized.value = newVal
updateRoute()
})
watch(() => props.coordinates, () => {
updateRoute()
}, { deep: true })
</script>
<style>
.map-container {
position: relative;
background-color: #f0f8ff;
margin: 20px;
overflow: hidden;
touch-action: none;
transition: all 0.3s ease;
width: 100%;
height: 100%;
border: 1px solid #ddd;
border-radius: 8px;
}
.fullscreen {
position: fixed !important;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0 !important;
z-index: 1000;
border-radius: 0;
}
.fullscreen-btn {
position: absolute;
top: 20px;
right: 30px;
z-index: 1001;
padding: 10px 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.fullscreen-btn:hover {
background: rgba(0, 0, 0, 0.9);
transform: translateY(-1px);
}
</style>
// points
watch(() => props.points, (newPoints) => {
drawTrajectory(newPoints || [])
}, { deep: true, immediate: true })
</script>

View File

@ -218,7 +218,7 @@ function handleClose(formEl: any) {
//
function delSubItem(row: any) {
ElMessageBox.confirm(
'您确定要删除该项目吗?',
'您确定要删除该节点及其中的文件吗?',
'警告',
{
confirmButtonText: '确定',
@ -227,6 +227,8 @@ function delSubItem(row: any) {
}
)
.then(() => {
treeloading.value = true
loading.value = true
deleteNodesById({ id: row.id }).then((res: any) => {
if (res.code == 0) {
gettreedata()

View File

@ -67,7 +67,7 @@ function editproject(row: any) {
//
function delproject(row: any) {
ElMessageBox.confirm(
'您确定要删除该项目吗?',
'您确定要删除该项目及其中的节点和文件吗?',
'警告',
{
confirmButtonText: '确定',
@ -76,6 +76,7 @@ function delproject(row: any) {
}
)
.then(() => {
loading.value = true
deleteSdprojectById({ id: row.id }).then((res: any) => {
if (res.code == 0) {
getdata()
@ -102,7 +103,7 @@ function delprojectArr() {
ids.push(item.id)
})
ElMessageBox.confirm(
'您确定要删除这些项目吗?',
'您确定要删除这些项目及其中的节点和文件吗?',
'警告',
{
confirmButtonText: '确定',
@ -111,6 +112,7 @@ function delprojectArr() {
}
)
.then(() => {
loading.value = true
deleteSdprojectByIds({ ids: ids.join(',') }).then((res: any) => {
if (res.code == 0) {
ElMessage({

View File

@ -13,7 +13,7 @@ import { ElMessageBox, ElMessage, ElMain } from "element-plus";
import Page from '@/components/Pagination/page.vue';
import AudioPlayer from '@/components/file/preview/AudioPlayer.vue';
import { batchDeleteReq } from "@/api/file-operator";
import { tstaskList, getTsNodesTree, addTsNodes, updateTsNodes, deleteTsNodesById, tsFilesPage, addTsFiles, updateTsFiles, deleteTsFilesById, listTsFiles, deleteTsFilesByIds, compress, Decompression, compare, downloadToLocal, uploadToBackup, addTsFile, list, moveFileFolder, copyFileFolder, startSimpleNavi } from "@/api/datamanagement";
import { tstaskList, getTsNodesTree, addTsNodes, updateTsNodes, deleteTsNodesById, tsFilesPage, addTsFiles, updateTsFiles, deleteTsFilesById, listTsFiles, deleteTsFilesByIds, compress, Decompression, compare, downloadToLocal, uploadToBackup, addTsFile, list, moveFileFolder, copyFileFolder, startSimpleNavi,stopSimpleNavi } from "@/api/datamanagement";
import ZUpload from '@/components/file/ZUpload.vue'
import useFileUpload from "@/components/file/file/useFileUpload";
import useHeaderStorageList from "@/components/header/useHeaderStorageList";
@ -1412,7 +1412,6 @@ function openMap(row: any) {
}
function mapClose() {
mapTrajectory.value = false
closeSSE()
// index = 0
}
@ -1455,6 +1454,7 @@ function frequency(row: any) {
qvehuan.value = true
qvehuan1.value = true
lineData.value.length = 0
dynamicCoordinates.value.length = 0
}
})
}
@ -1462,11 +1462,11 @@ function frequency(row: any) {
const eventSource = ref(null)
let index = 0
const dynamicCoordinates = ref([])
let SSEclose
// let SSEclose
function closeSSE() {
dynamicCoordinates.value.length = 0
// SSEclose :any
SSEclose = new EventSource(userStore.webApiBaseUrl + '/sse/disconnect/' + userStore.userId)
stopSimpleNavi({ token: userStore.userId }).then((res: any) => {
})
eventSource.value?.close()
}
function getSSELink() {
@ -1482,10 +1482,7 @@ function getSSELink() {
const data = JSON.parse(e.data)
console.log('SSE消息:', data)
if (data) {
dynamicCoordinates.value.push([data.lon, data.lat])
// if (dynamicCoordinates.value.length > 2) {
// dynamicCoordinates.value.shift()
// }
dynamicCoordinates.value.push([data.lat, data.lon])
lineData.value.push({ x: data.UtcTime, y: data.alt })
}
} catch (err) {
@ -1919,8 +1916,8 @@ function texexceltClose() {
<div @click="tabbas = '1'" :class="tabbas == '1' ? 'mapbox_border' : 'mapbox_border1'">载体运动轨迹</div>
<div @click="tabbas = '2'" :class="tabbas == '2' ? 'mapbox_border' : 'mapbox_border1'">高程变化动态图</div>
</div>
<div v-show="tabbas == '1'" style="width: 800px;height:600px;overflow: hidden;">
<MapChart :coordinates="dynamicCoordinates" :qvehuan="qvehuan" @qvehuan1="handleCustomEvent" />
<div v-show="tabbas == '1'" style="width:100%;height:600px;overflow: hidden;margin-top: 20px;">
<MapChart :points="dynamicCoordinates" />
</div>
<div v-show="tabbas == '2'" style="width: 800px;height:600px;overflow: hidden;">
<Echart :chart-data="lineData" />

View File

@ -91,7 +91,7 @@ function editproject(row: any) {
//
function delproject(row: any) {
ElMessageBox.confirm(
'您确定要删除该项目吗?',
'您确定要删除该项目及其中的节点和文件吗?',
'警告',
{
confirmButtonText: '确定',
@ -100,6 +100,7 @@ function delproject(row: any) {
}
)
.then(() => {
loading.value = true
deleteTsTaskById({ id: row.id }).then((res: any) => {
if (res.code == 0) {
getdata()
@ -123,7 +124,7 @@ function delprojectArr() {
ids.push(item.id)
})
ElMessageBox.confirm(
'您确定要删除该项目吗?',
'您确定要删除这些项目及其中的节点和文件吗?',
'警告',
{
confirmButtonText: '确定',
@ -132,6 +133,7 @@ function delprojectArr() {
}
)
.then(() => {
loading.value = true
deleteTsTaskByIds({ ids: ids.join(',') }).then((res: any) => {
if (res.code == 0) {
ElMessage({