图层使用websocke动态数据

This commit is contained in:
renna 2025-07-08 16:59:39 +08:00
parent a9c66a376a
commit 921c2e8e5d
14 changed files with 1568 additions and 728 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

View File

@ -42,6 +42,7 @@ $--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
$base-sidebar-width: 280px;
// the :export directive is the magic sauce for webpack

View File

@ -8,10 +8,13 @@
<OpenLayersZoomControl
:map="map"
:resetView="resetView"
:vehicleCategories="vehicleCategories"
:vehicleMovementControl="vehicleMovementControl"
@compass="compass"
@zoomIn="zoomIn"
@zoomOut="zoomOut"
@layerChange="handleLayerChange"
@setCategoryVisibility="handleSetCategoryVisibility"
/>
<!-- 地图信息 -->
@ -50,6 +53,26 @@ import ScaleLine from 'ol/control/ScaleLine';
import OpenLayersScaleControl from "./controls/OpenLayersScaleControl.vue";
import RouteDrawControl from "./controls/RouteDrawControl.vue";
// props
const props = defineProps({
vehicleCategories: {
type: Object,
default: () => ({})
},
vehicleMovementControl: {
type: Object,
default: null
}
});
//
const emit = defineEmits(['setCategoryVisibility']);
//
function handleSetCategoryVisibility(type, settings) {
emit('setCategoryVisibility', type, settings);
}
// EPSG:4528
proj4.defs(
"EPSG:4528",

View File

@ -104,7 +104,24 @@
</div>
</div>
<div class="panel-content" v-else-if="activeTab === 'road'">
<div class="panel-content" v-else-if="activeTab === 'text'">
<div class="layer-group">
<div class="group-title">文本显示</div>
<div class="layer-grid">
<div class="layer-item" v-for="layer in exampleVehicleLayers" :key="'label-'+layer.id">
<label class="checkbox-container">
<input type="checkbox" :checked="layer.showLabel" @change="toggleExampleLabel(layer)">
<span class="checkmark"></span>
<span class="layer-name">{{ layer.name }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="panel-content" v-else>
<div class="layer-group">
<div class="group-title">道路图层</div>
<div class="layer-grid-full">
@ -128,27 +145,6 @@
</div>
</div>
</div>
<div class="panel-content" v-else>
<!-- 文本标签页内容 -->
<div class="layer-group">
<div class="group-title">文本样式</div>
<div class="style-selector">
<div class="style-item">
<div class="style-label">默认样式</div>
<div class="radio-box" :class="{ active: selectedTextStyle === 'default' }" @click="selectTextStyle('default')"></div>
</div>
<div class="style-item">
<div class="style-label">蓝色样式</div>
<div class="radio-box blue" :class="{ active: selectedTextStyle === 'blue' }" @click="selectTextStyle('blue')"></div>
</div>
<div class="style-item">
<div class="style-label">白色样式</div>
<div class="radio-box white" :class="{ active: selectedTextStyle === 'white' }" @click="selectTextStyle('white')"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@ -161,11 +157,16 @@ import { Style, Icon, Stroke, Fill } from 'ol/style';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import GeoJSON from 'ol/format/GeoJSON';
import Overlay from 'ol/Overlay';
import labelBg from '../../../assets/images/label_bg.png';
//
import car1Icon from '../../../assets/images/Aircraft.png';
import car1Icon from '../../../assets/images/Aircraft.png'; //
import car1Icon1 from '../../../assets/images/Aircraft1.png'; //
import car2Icon from '../../../assets/images/noPeopleCar.png';
import noPeopleCarIcon from '../../../assets/images/noPeopleCar.png';
import airportBg from '../../../assets/images/airport_bg.png'; //
import airportOutBg from '../../../assets/images/airport_out.png'; //
// props
const props = defineProps({
@ -192,21 +193,25 @@ const exampleVehicleLayers = ref([
id: 'car1-layer',
name: '滑入航空器',
visible: true,
icon: car1Icon,
showLabel: true,
icon: car1Icon1,
type: 'aircraft-in',
layer: null,
features: [
{ id: 'car1-1', position: [40507699.051041, 4026243.105796], name: '滑入航空器1' },
{ id: 'car1-2', position: [40508097.909698, 4026315.085762], name: '滑入航空器2' }
{ id: 'car1-1', position: [40507699.051041, 4026243.105796], name: '滑入航空器1', speed: 50 },
{ id: 'car1-2', position: [40508097.909698, 4026315.085762], name: '滑入航空器2', speed: 50 }
]
},
{
id: 'car1-layer1',
name: '滑出航空器',
visible: true,
showLabel: true,
icon: car1Icon,
type: 'aircraft-out',
layer: null,
features: [
{ id: 'car2-1', position: [4.0507e7, 4024000], name: '滑出航空器1' },
{ id: 'car2-1', position: [4.0507e7, 4024000], name: '滑出航空器1', speed: 50 },
]
},
@ -214,11 +219,13 @@ const exampleVehicleLayers = ref([
id: 'no-people-car',
name: '无人车全选',
visible: true,
showLabel: true,
icon: noPeopleCarIcon,
type: 'car',
layer: null,
features: [
{ id: 'no-people-car-1', position: [40508392.141630, 4026279.431719], name: '无人车1' },
{ id: 'no-people-car-2', position: [40507625.995559, 4025622.462289], name: '无人车2' }
{ id: 'no-people-car-1', position: [40508392.141630, 4026279.431719], name: '无人车1', speed: 17 },
{ id: 'no-people-car-2', position: [40507625.995559, 4025622.462289], name: '无人车2', speed: 17 }
]
}
]);
@ -248,17 +255,7 @@ const vehicleLayers = ref([
{ id: 'forklift', name: '叉车', visible: false, layer: null }
]);
//
const textStyles = ref([
{ id: 'slip-in-plane', name: '滑入航空器', visible: true, layer: null },
{ id: 'slip-out-plane', name: '滑出航空器', visible: true, layer: null },
{ id: 'uav', name: '无人车全选', visible: true, layer: null },
{ id: 'driving-car', name: '驱鸟车', visible: true, layer: null },
{ id: 'push-car', name: '摆渡车', visible: true, layer: null },
{ id: 'military-car', name: '军引车', visible: true, layer: null },
{ id: 'special-car', name: '特勤车全选', visible: false, layer: null },
{ id: 'fire-car', name: '消防车', visible: false, layer: null },
]);
//
const otherVehicleLayers = computed(() => {
@ -292,6 +289,32 @@ function toggleExampleLayer(layer) {
layer.layer.setVisible(layer.visible);
}
//
layer.features.forEach(vehicle => {
if (vehicle.overlay) {
if (layer.visible && layer.showLabel) {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
} catch (e) {
//
}
props.map.addOverlay(vehicle.overlay);
if (!allLabelOverlays.includes(vehicle.overlay)) {
allLabelOverlays.push(vehicle.overlay);
}
} else {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
allLabelOverlays = allLabelOverlays.filter(o => o !== vehicle.overlay);
} catch (e) {
//
}
}
}
});
//
emit('layerChange', {
type: 'exampleLayer',
@ -299,16 +322,24 @@ function toggleExampleLayer(layer) {
});
}
//
// label overlay便
let allLabelOverlays = [];
function removeAllLabelOverlays() {
if (!props.map) return;
allLabelOverlays.forEach(overlay => {
props.map.removeOverlay(overlay);
});
allLabelOverlays = [];
}
function initExampleLayers() {
if (!props.map) return;
removeAllLabelOverlays(); // label overlay
allLabelOverlays = []; //
//
exampleVehicleLayers.value.forEach(layerConfig => {
//
const source = new VectorSource();
//
const style = new Style({
image: new Icon({
src: layerConfig.icon,
@ -316,36 +347,71 @@ function initExampleLayers() {
anchor: [0.5, 0.5]
})
});
//
layerConfig.features.forEach(vehicle => {
const feature = new Feature({
geometry: new Point(vehicle.position),
name: vehicle.name,
id: vehicle.id
});
feature.setStyle(style);
feature.setStyle(style); //
source.addFeature(feature);
//
const labelDiv = document.createElement('div');
labelDiv.className = `custom-label label-${layerConfig.type}`;
// - 使(airport_out.png)使(airport_bg.png)
let backgroundImage;
if (layerConfig.type === 'aircraft-in') {
backgroundImage = airportOutBg; // -
} else if (layerConfig.type === 'aircraft-out') {
backgroundImage = airportBg; // -
} else {
backgroundImage = labelBg; //
}
labelDiv.style.backgroundImage = `url(${backgroundImage})`;
labelDiv.style.backgroundRepeat = 'no-repeat';
labelDiv.style.backgroundSize = '100% 100%';
labelDiv.style.padding = '3px 10px';
labelDiv.innerText =
layerConfig.type === 'aircraft-in'
? `航空器在线 ${vehicle.speed}km/h`
: layerConfig.type === 'aircraft-out'
? `航空器在线 ${vehicle.speed}km/h`
: layerConfig.type === 'car'
? `${vehicle.name.replace('无人车', 'QN00')}在线 ${vehicle.speed}km/h`
: vehicle.name;
// overlay
const overlay = new Overlay({
element: labelDiv,
position: vehicle.position,
positioning: 'bottom-center',
offset: [0, -20], // 使
stopEvent: false
});
vehicle.overlay = overlay;
// showLabeltrueaddOverlay
if (layerConfig.showLabel && layerConfig.visible) {
props.map.addOverlay(overlay);
allLabelOverlays.push(overlay);
}
});
//
const vectorLayer = new VectorLayer({
source: source,
visible: layerConfig.visible,
zIndex: 10 //
zIndex: 10
});
//
layerConfig.layer = vectorLayer;
//
props.map.addLayer(vectorLayer);
//
vectorLayer.setVisible(layerConfig.visible);
console.log(`初始化图层: ${layerConfig.id}, 可见性: ${layerConfig.visible}`);
console.log(`初始化图层: ${layerConfig.id}, 图标可见性: ${layerConfig.visible}, 文本可见性: ${layerConfig.showLabel}`);
});
}
@ -378,6 +444,47 @@ function toggleTextStyle(textStyle) {
//
function selectTextStyle(style) {
selectedTextStyle.value = style;
console.log('选择文本样式:', style);
//
const allLabels = document.querySelectorAll('.custom-label');
allLabels.forEach(label => {
//
label.style.backgroundImage = labelBg;
label.style.backgroundRepeat = "no-repeat";
label.style.backgroundSize = "100% 100%";
//
switch(style) {
case 'blue':
//
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff';
break;
case 'white':
//
// label.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
// label.style.borderColor = '#ffffff';
label.style.color = '#333';
break;
default:
//
if (label.classList.contains('label-aircraft-in')) {
// label.style.backgroundColor = 'rgba(245, 231, 79, 0.7)';
// label.style.borderColor = '#E4CB0D';
label.style.color = '#333'; // -
} else if (label.classList.contains('label-aircraft-out')) {
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff'; // -
} else if (label.classList.contains('label-car')) {
// label.style.backgroundColor = 'rgba(37, 37, 37, 0.7)';
// label.style.borderColor = '#484848';
label.style.color = '#fff';
}
}
});
//
emit('layerChange', {
@ -386,6 +493,56 @@ function selectTextStyle(style) {
});
}
//
function updateAllLabelsStyle(styleName) {
console.log('更新文本样式为:', styleName);
//
const allLabels = document.querySelectorAll('.custom-label');
allLabels.forEach(label => {
label.classList.remove('style-default', 'style-blue', 'style-white');
label.classList.add(`style-${styleName}`);
//
const isAircraftIn = label.classList.contains('label-aircraft-in');
const isAircraftOut = label.classList.contains('label-aircraft-out');
const isCar = label.classList.contains('label-car');
//
switch(styleName) {
case 'blue':
//
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff';
label.style.fontSize = '12px';
label.style.background = 'url(../../../assets/images/label_bg.png) no-repeat';
break;
case 'white':
//
// label.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
// label.style.borderColor = '#ffffff';
label.style.color = '#333';
break;
default:
//
if (isAircraftIn) {
// label.style.backgroundColor = 'rgba(245, 231, 79, 0.7)';
// label.style.borderColor = '#E4CB0D';
label.style.color = '#333'; // -
} else if (isAircraftOut) {
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff'; // -
} else if (isCar) {
// label.style.backgroundColor = 'rgba(37, 37, 37, 0.7)';
// label.style.borderColor = '#484848';
label.style.color = '#fff';
}
}
});
}
//
function initLayers() {
if (!props.map) return;
@ -438,26 +595,45 @@ function toggleHideRoadLayer() {
//
onMounted(() => {
if (props.map) {
//
initLayers();
// 线
loadCustomRoadLayer();
// -
if (showRoadLayer.value) {
addRoadLayer();
}
//
setTimeout(() => {
console.log('组件挂载后应用默认样式');
updateAllLabelsStyle('default');
}, 200);
}
});
//
watch(() => props.map, (newMap) => {
if (newMap) {
//
initLayers();
// 线
loadCustomRoadLayer();
//
if (showRoadLayer.value) {
addRoadLayer();
}
//
setTimeout(() => {
console.log('地图实例变化后应用默认样式');
// selectTextStyle
selectTextStyle('default');
}, 200);
}
});
@ -635,6 +811,47 @@ onUnmounted(() => {
removeCustomRoadLayer();
stopFenceFlashing();
});
// label
function toggleExampleLabel(layer) {
layer.showLabel = !layer.showLabel;
console.log(`切换${layer.name}文本显示:`, layer.showLabel);
// featureoverlay
layer.features.forEach(vehicle => {
if (vehicle.overlay) {
if (layer.showLabel) {
//
try {
props.map.removeOverlay(vehicle.overlay);
} catch (e) {
console.log('移除overlay失败可能不存在:', e);
}
// overlay
props.map.addOverlay(vehicle.overlay);
if (!allLabelOverlays.includes(vehicle.overlay)) {
allLabelOverlays.push(vehicle.overlay);
}
} else {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
allLabelOverlays = allLabelOverlays.filter(o => o !== vehicle.overlay);
} catch (e) {
console.log('移除overlay失败:', e);
}
}
}
});
//
emit('layerChange', {
type: 'labelChange',
layer: layer,
showLabel: layer.showLabel
});
}
</script>
<style scoped>
@ -643,8 +860,8 @@ onUnmounted(() => {
}
.layer-icon {
width: 40px;
height: 40px;
width: 32px;
height: 32px;
cursor: pointer;
}
@ -798,8 +1015,8 @@ onUnmounted(() => {
/* 图层图标预览 */
.layer-icon-preview {
width: 24px;
height: 24px;
width: 20px;
height: 20px;
margin-left: 8px;
object-fit: contain;
}
@ -901,4 +1118,63 @@ onUnmounted(() => {
white-space: pre-wrap;
word-break: break-all;
}
/* 自定义标签样式 */
.custom-label {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
min-width: 120px;
height: 28px;
padding: 0 10px;
font-size: 12px;
font-weight: bold;
border-radius: 4px;
border: 1px solid;
color: #fff;
box-sizing: border-box;
white-space: nowrap;
z-index: 1000;
pointer-events: none;
transform: translateX(-50%);
}
/* 滑入航空器 - 黄色背景,黑色文字 */
.label-aircraft-in {
background-color: rgba(245, 231, 79, 0.7);
border-color: #E4CB0D;
color: #333;
}
/* 滑出航空器 - 蓝色背景,白色文字 */
.label-aircraft-out {
background-color: rgba(52, 122, 226, 0.7);
border-color: #347AE2;
color: #fff;
}
/* 无人车 */
.label-car {
background-color: rgba(37, 37, 37, 0.7);
border-color: #484848;
color: #fff;
}
/* 文本样式类 */
.custom-label.style-default {
/* 默认样式在各个类型中已定义 */
}
.custom-label.style-blue {
background-color: rgba(52, 122, 226, 0.7) !important;
border-color: #347AE2 !important;
color: #fff !important;
}
.custom-label.style-white {
background-color: rgba(255, 255, 255, 0.7) !important;
border-color: #ffffff !important;
color: #333 !important;
}
</style>

View File

@ -19,7 +19,7 @@
@click="activeTab = 'text'"
>
文本
</div>
</div>
<div
class="tab"
:class="{ active: activeTab === 'road' }"
@ -29,96 +29,37 @@
</div>
</div>
<!-- 图标tab页 -->
<div class="panel-content" v-if="activeTab === 'icon'">
<!-- 航空器图层 -->
<div class="layer-group">
<div class="group-title">航空器</div>
<!-- <div class="group-title">图标显示控制</div> -->
<div class="layer-grid">
<div class="layer-item" v-for="layer in exampleVehicleLayers.filter(l => l.id !== 'no-people-car')" :key="'example-'+layer.id">
<div class="layer-item" v-for="(cat, type) in categories" :key="type">
<label class="checkbox-container">
<input type="checkbox" :checked="layer.visible" @change="toggleExampleLayer(layer)">
<input type="checkbox" v-model="cat.visible" @change="emitSet(type)">
<span class="checkmark"></span>
<span class="layer-name">{{ layer.name }}</span>
<img :src="layer.icon" class="layer-icon-preview" />
</label>
</div>
</div>
</div>
<!-- 无人车全选 -->
<div class="layer-group">
<div class="group-title">无人车</div>
<div class="layer-grid-full">
<div class="layer-item">
<label class="checkbox-container">
<input type="checkbox" :checked="getExampleLayerById('no-people-car').visible" @change="toggleExampleLayer(getExampleLayerById('no-people-car'))">
<span class="checkmark"></span>
<span class="layer-name">无人车全选</span>
<img :src="noPeopleCarIcon" class="layer-icon-preview" />
</label>
</div>
</div>
</div>
<!-- 常用车辆 -->
<div class="layer-group">
<div class="group-title">常用车辆</div>
<div class="layer-grid">
<div class="layer-item">
<label class="checkbox-container">
<input type="checkbox" :checked="getLayerById('driving-car').visible" @change="toggleVehicleLayer(getLayerById('driving-car'))">
<span class="checkmark"></span>
<span class="layer-name">驱鸟车</span>
</label>
</div>
<div class="layer-item">
<label class="checkbox-container">
<input type="checkbox" :checked="getLayerById('push-car').visible" @change="toggleVehicleLayer(getLayerById('push-car'))">
<span class="checkmark"></span>
<span class="layer-name">摆渡车</span>
</label>
</div>
<div class="layer-item">
<label class="checkbox-container">
<input type="checkbox" :checked="getLayerById('military-car').visible" @change="toggleVehicleLayer(getLayerById('military-car'))">
<span class="checkmark"></span>
<span class="layer-name">牵引车</span>
</label>
</div>
</div>
</div>
<!-- 其他车辆图层 -->
<div class="layer-group">
<div class="group-title">其他车辆</div>
<div class="layer-grid">
<div class="layer-item" v-for="layer in otherVehicleLayers" :key="'vehicle-'+layer.id">
<label class="checkbox-container">
<input type="checkbox" :checked="layer.visible" @change="toggleVehicleLayer(layer)">
<span class="checkmark"></span>
<span class="layer-name">{{ layer.name }}</span>
<span class="layer-name">{{ cat.name }}</span>
<!-- <img :src="cat.icon" class="layer-icon-preview" /> -->
</label>
</div>
</div>
</div>
</div>
<!-- 文本tab页 -->
<div class="panel-content" v-else-if="activeTab === 'text'">
<div class="layer-group">
<div class="group-title">文本显示</div>
<!-- <div class="group-title">标签显示控制</div> -->
<div class="layer-grid">
<div class="layer-item" v-for="layer in exampleVehicleLayers" :key="'label-'+layer.id">
<div class="layer-item" v-for="(cat, type) in categories" :key="type">
<label class="checkbox-container">
<input type="checkbox" :checked="layer.showLabel" @change="toggleExampleLabel(layer)">
<input type="checkbox" v-model="cat.showLabel" @change="emitSet(type)">
<span class="checkmark"></span>
<span class="layer-name">{{ layer.name }}</span>
<span class="layer-name">{{ cat.name }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="panel-content" v-else>
@ -130,7 +71,7 @@
<input type="checkbox" :checked="!hideRoadLayer" @change="toggleHideRoadLayer" />
<span class="checkmark"></span>
<span class="layer-name">电子围栏</span>
<svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#409eff"/></svg>
<!-- <svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#409eff"/></svg> -->
</label>
</div>
<!-- 添加自定义路线图层选项 -->
@ -139,7 +80,7 @@
<input type="checkbox" v-model="showCustomRoadLayer" />
<span class="checkmark"></span>
<span class="layer-name">路线图</span>
<svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#FF5722"/></svg>
<!-- <svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#FF5722"/></svg> -->
</label>
</div>
</div>
@ -170,7 +111,8 @@ import airportOutBg from '../../../assets/images/airport_out.png'; // 黄色背
// props
const props = defineProps({
map: Object
map: Object,
categories: Object
});
//
@ -187,399 +129,14 @@ let roadVectorLayer = null;
const showCustomRoadLayer = ref(true); //
let customRoadVectorLayer = null;
// -
const exampleVehicleLayers = ref([
{
id: 'car1-layer',
name: '滑入航空器',
visible: true,
showLabel: true,
icon: car1Icon1,
type: 'aircraft-in',
layer: null,
features: [
{ id: 'car1-1', position: [40507699.051041, 4026243.105796], name: '滑入航空器1', speed: 50 },
{ id: 'car1-2', position: [40508097.909698, 4026315.085762], name: '滑入航空器2', speed: 50 }
]
},
{
id: 'car1-layer1',
name: '滑出航空器',
visible: true,
showLabel: true,
icon: car1Icon,
type: 'aircraft-out',
layer: null,
features: [
{ id: 'car2-1', position: [4.0507e7, 4024000], name: '滑出航空器1', speed: 50 },
]
},
{
id: 'no-people-car',
name: '无人车全选',
visible: true,
showLabel: true,
icon: noPeopleCarIcon,
type: 'car',
layer: null,
features: [
{ id: 'no-people-car-1', position: [40508392.141630, 4026279.431719], name: '无人车1', speed: 17 },
{ id: 'no-people-car-2', position: [40507625.995559, 4025622.462289], name: '无人车2', speed: 17 }
]
}
]);
// -
const vehicleLayers = ref([
{ id: 'slip-in-plane', name: '滑入航空器', visible: true, layer: null },
{ id: 'slip-out-plane', name: '滑出航空器', visible: true, layer: null },
{ id: 'uav', name: '无人车全选', visible: true, layer: null },
{ id: 'driving-car', name: '驱鸟车', visible: true, layer: null },
{ id: 'push-car', name: '摆渡车', visible: true, layer: null },
{ id: 'military-car', name: '牵引车', visible: true, layer: null },
{ id: 'special-car', name: '特勤车全选', visible: false, layer: null },
{ id: 'fire-car', name: '消防车', visible: false, layer: null },
{ id: 'water-car', name: '清水车', visible: false, layer: null },
{ id: 'patrol-car', name: '巡逻车', visible: false, layer: null },
{ id: 'emergency-car', name: '急救车', visible: false, layer: null },
{ id: 'container-car', name: '客梯车', visible: false, layer: null },
{ id: 'small-car', name: '小型客车', visible: false, layer: null },
{ id: 'maintenance-car', name: '维修车', visible: false, layer: null },
{ id: 'tool-car', name: '工具车', visible: false, layer: null },
{ id: 'tour-car', name: '巡游车', visible: false, layer: null },
{ id: 'sewage-car', name: '污水车', visible: false, layer: null },
{ id: 'road-car', name: '道路车', visible: false, layer: null },
{ id: 'garbage-car', name: '垃圾车', visible: false, layer: null },
{ id: 'police-car', name: '警察车', visible: false, layer: null },
{ id: 'forklift', name: '叉车', visible: false, layer: null }
]);
//
const otherVehicleLayers = computed(() => {
const excludedIds = ['uav', 'driving-car', 'push-car', 'military-car', 'slip-in-plane', 'slip-out-plane'];
return vehicleLayers.value.filter(layer => !excludedIds.includes(layer.id));
});
// ID
function getLayerById(id) {
return vehicleLayers.value.find(layer => layer.id === id) || { visible: false };
}
// ID
function getExampleLayerById(id) {
return exampleVehicleLayers.value.find(layer => layer.id === id) || { visible: false };
}
//
const emit = defineEmits(['layerChange']);
const emit = defineEmits(['layerChange', 'setCategoryVisibility']);
//
function toggleLayerPanel() {
showPanel.value = !showPanel.value;
}
//
function toggleExampleLayer(layer) {
layer.visible = !layer.visible;
if (layer.layer) {
layer.layer.setVisible(layer.visible);
}
//
layer.features.forEach(vehicle => {
if (vehicle.overlay) {
if (layer.visible && layer.showLabel) {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
} catch (e) {
//
}
props.map.addOverlay(vehicle.overlay);
if (!allLabelOverlays.includes(vehicle.overlay)) {
allLabelOverlays.push(vehicle.overlay);
}
} else {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
allLabelOverlays = allLabelOverlays.filter(o => o !== vehicle.overlay);
} catch (e) {
//
}
}
}
});
//
emit('layerChange', {
type: 'exampleLayer',
layer: layer
});
}
// label overlay便
let allLabelOverlays = [];
function removeAllLabelOverlays() {
if (!props.map) return;
allLabelOverlays.forEach(overlay => {
props.map.removeOverlay(overlay);
});
allLabelOverlays = [];
}
function initExampleLayers() {
if (!props.map) return;
removeAllLabelOverlays(); // label overlay
allLabelOverlays = []; //
exampleVehicleLayers.value.forEach(layerConfig => {
const source = new VectorSource();
const style = new Style({
image: new Icon({
src: layerConfig.icon,
scale: 1.5,
anchor: [0.5, 0.5]
})
});
layerConfig.features.forEach(vehicle => {
const feature = new Feature({
geometry: new Point(vehicle.position),
name: vehicle.name,
id: vehicle.id
});
feature.setStyle(style); //
source.addFeature(feature);
//
const labelDiv = document.createElement('div');
labelDiv.className = `custom-label label-${layerConfig.type}`;
// - 使(airport_out.png)使(airport_bg.png)
let backgroundImage;
if (layerConfig.type === 'aircraft-in') {
backgroundImage = airportOutBg; // -
} else if (layerConfig.type === 'aircraft-out') {
backgroundImage = airportBg; // -
} else {
backgroundImage = labelBg; //
}
labelDiv.style.backgroundImage = `url(${backgroundImage})`;
labelDiv.style.backgroundRepeat = 'no-repeat';
labelDiv.style.backgroundSize = '100% 100%';
labelDiv.style.padding = '3px 10px';
labelDiv.innerText =
layerConfig.type === 'aircraft-in'
? `航空器在线 ${vehicle.speed}km/h`
: layerConfig.type === 'aircraft-out'
? `航空器在线 ${vehicle.speed}km/h`
: layerConfig.type === 'car'
? `${vehicle.name.replace('无人车', 'QN00')}在线 ${vehicle.speed}km/h`
: vehicle.name;
// overlay
const overlay = new Overlay({
element: labelDiv,
position: vehicle.position,
positioning: 'bottom-center',
offset: [0, -20], // 使
stopEvent: false
});
vehicle.overlay = overlay;
// showLabeltrueaddOverlay
if (layerConfig.showLabel && layerConfig.visible) {
props.map.addOverlay(overlay);
allLabelOverlays.push(overlay);
}
});
const vectorLayer = new VectorLayer({
source: source,
visible: layerConfig.visible,
zIndex: 10
});
layerConfig.layer = vectorLayer;
props.map.addLayer(vectorLayer);
vectorLayer.setVisible(layerConfig.visible);
console.log(`初始化图层: ${layerConfig.id}, 图标可见性: ${layerConfig.visible}, 文本可见性: ${layerConfig.showLabel}`);
});
}
//
function toggleVehicleLayer(layer) {
layer.visible = !layer.visible;
if (layer.layer) {
layer.layer.setVisible(layer.visible);
}
//
emit('layerChange', {
type: 'vehicleLayer',
layer: layer
});
}
//
function toggleTextStyle(textStyle) {
textStyle.visible = !textStyle.visible;
//
emit('layerChange', {
type: 'textStyle',
style: textStyle
});
}
//
function selectTextStyle(style) {
selectedTextStyle.value = style;
console.log('选择文本样式:', style);
//
const allLabels = document.querySelectorAll('.custom-label');
allLabels.forEach(label => {
//
label.style.backgroundImage = labelBg;
label.style.backgroundRepeat = "no-repeat";
label.style.backgroundSize = "100% 100%";
//
switch(style) {
case 'blue':
//
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff';
break;
case 'white':
//
// label.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
// label.style.borderColor = '#ffffff';
label.style.color = '#333';
break;
default:
//
if (label.classList.contains('label-aircraft-in')) {
// label.style.backgroundColor = 'rgba(245, 231, 79, 0.7)';
// label.style.borderColor = '#E4CB0D';
label.style.color = '#333'; // -
} else if (label.classList.contains('label-aircraft-out')) {
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff'; // -
} else if (label.classList.contains('label-car')) {
// label.style.backgroundColor = 'rgba(37, 37, 37, 0.7)';
// label.style.borderColor = '#484848';
label.style.color = '#fff';
}
}
});
//
emit('layerChange', {
type: 'selectTextStyle',
style: style
});
}
//
function updateAllLabelsStyle(styleName) {
console.log('更新文本样式为:', styleName);
//
const allLabels = document.querySelectorAll('.custom-label');
allLabels.forEach(label => {
label.classList.remove('style-default', 'style-blue', 'style-white');
label.classList.add(`style-${styleName}`);
//
const isAircraftIn = label.classList.contains('label-aircraft-in');
const isAircraftOut = label.classList.contains('label-aircraft-out');
const isCar = label.classList.contains('label-car');
//
switch(styleName) {
case 'blue':
//
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff';
label.style.fontSize = '12px';
label.style.background = 'url(../../../assets/images/label_bg.png) no-repeat';
break;
case 'white':
//
// label.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
// label.style.borderColor = '#ffffff';
label.style.color = '#333';
break;
default:
//
if (isAircraftIn) {
// label.style.backgroundColor = 'rgba(245, 231, 79, 0.7)';
// label.style.borderColor = '#E4CB0D';
label.style.color = '#333'; // -
} else if (isAircraftOut) {
// label.style.backgroundColor = 'rgba(52, 122, 226, 0.7)';
// label.style.borderColor = '#347AE2';
label.style.color = '#fff'; // -
} else if (isCar) {
// label.style.backgroundColor = 'rgba(37, 37, 37, 0.7)';
// label.style.borderColor = '#484848';
label.style.color = '#fff';
}
}
});
}
//
function initLayers() {
if (!props.map) return;
//
initExampleLayers();
}
//
function setLayerVisibility(layerId, visible) {
//
const exampleLayer = exampleVehicleLayers.value.find(layer => layer.id === layerId);
if (exampleLayer) {
exampleLayer.visible = visible;
if (exampleLayer.layer) {
exampleLayer.layer.setVisible(visible);
}
return true;
}
//
const vehicleLayer = vehicleLayers.value.find(layer => layer.id === layerId);
if (vehicleLayer) {
vehicleLayer.visible = visible;
//
emit('layerChange', {
type: 'vehicleLayer',
layer: vehicleLayer
});
return true;
}
return false;
}
//
function toggleHideRoadLayer() {
hideRoadLayer.value = !hideRoadLayer.value;
@ -595,9 +152,6 @@ function toggleHideRoadLayer() {
//
onMounted(() => {
if (props.map) {
//
initLayers();
// 线
loadCustomRoadLayer();
@ -605,21 +159,12 @@ onMounted(() => {
if (showRoadLayer.value) {
addRoadLayer();
}
//
setTimeout(() => {
console.log('组件挂载后应用默认样式');
updateAllLabelsStyle('default');
}, 200);
}
});
//
watch(() => props.map, (newMap) => {
if (newMap) {
//
initLayers();
// 线
loadCustomRoadLayer();
@ -627,19 +172,17 @@ watch(() => props.map, (newMap) => {
if (showRoadLayer.value) {
addRoadLayer();
}
//
setTimeout(() => {
console.log('地图实例变化后应用默认样式');
// selectTextStyle
selectTextStyle('default');
}, 200);
}
});
//
defineExpose({
setLayerVisibility
setLayerVisibility(typeKey, visible) {
emit('setCategoryVisibility', typeKey, {
visible,
showLabel: props.categories[typeKey]?.showLabel || true
});
}
});
async function loadCustomRoadLayer() {
@ -812,44 +355,11 @@ onUnmounted(() => {
stopFenceFlashing();
});
// label
function toggleExampleLabel(layer) {
layer.showLabel = !layer.showLabel;
console.log(`切换${layer.name}文本显示:`, layer.showLabel);
// featureoverlay
layer.features.forEach(vehicle => {
if (vehicle.overlay) {
if (layer.showLabel) {
//
try {
props.map.removeOverlay(vehicle.overlay);
} catch (e) {
console.log('移除overlay失败可能不存在:', e);
}
// overlay
props.map.addOverlay(vehicle.overlay);
if (!allLabelOverlays.includes(vehicle.overlay)) {
allLabelOverlays.push(vehicle.overlay);
}
} else {
// overlay
try {
props.map.removeOverlay(vehicle.overlay);
allLabelOverlays = allLabelOverlays.filter(o => o !== vehicle.overlay);
} catch (e) {
console.log('移除overlay失败:', e);
}
}
}
});
//
emit('layerChange', {
type: 'labelChange',
layer: layer,
showLabel: layer.showLabel
//
function emitSet(type) {
emit('setCategoryVisibility', type, {
visible: props.categories[type].visible,
showLabel: props.categories[type].showLabel
});
}
</script>

View File

@ -3,7 +3,9 @@
<LayerSwitcher
class="layer-switcher"
:map="map"
:categories="vehicleCategories"
@layerChange="onLayerChange"
@setCategoryVisibility="handleSetCategoryVisibility"
ref="layerSwitcherRef"
/>
@ -37,6 +39,14 @@ const props = defineProps({
type: Function,
default: null,
},
vehicleCategories: {
type: Object,
default: () => ({})
},
vehicleMovementControl: {
type: Object,
default: null
}
});
//
@ -52,6 +62,7 @@ const emit = defineEmits([
"zoomOut",
"layerChange",
"rotate",
"setCategoryVisibility"
]);
//
@ -144,6 +155,17 @@ function onLayerChange(layerInfo) {
emit("layerChange", layerInfo);
}
//
function handleSetCategoryVisibility(type, settings) {
//
emit('setCategoryVisibility', type, settings);
// propsvehicleMovementControl
if (props.vehicleMovementControl && props.vehicleMovementControl.setCategoryVisibility) {
props.vehicleMovementControl.setCategoryVisibility(type, settings);
}
}
//
let rotationListener = null;
@ -152,7 +174,7 @@ onMounted(() => {
//
updateRotation();
//
// - 使on
rotationListener = props.map
.getView()
.on("change:rotation", updateRotation);
@ -160,9 +182,18 @@ onMounted(() => {
});
onUnmounted(() => {
//
if (rotationListener) {
rotationListener.dispose();
// - 使OpenLayers
if (rotationListener && props.map) {
try {
// 使unByKey
import('ol/Observable').then(({ unByKey }) => {
unByKey(rotationListener);
}).catch(e => {
console.error('清理事件监听器失败:', e);
});
} catch (e) {
console.error('清理事件监听器失败:', e);
}
}
});
@ -176,7 +207,15 @@ watch(
//
if (rotationListener) {
rotationListener.dispose();
try {
import('ol/Observable').then(({ unByKey }) => {
unByKey(rotationListener);
}).catch(e => {
console.error('清理事件监听器失败:', e);
});
} catch (e) {
console.error('清理事件监听器失败:', e);
}
}
//
@ -283,9 +322,10 @@ defineExpose({
cursor: pointer;
}
.layer-switcher:hover,
.layer-switcher:active {
.layer-switcher.active {
background: url("../../../assets/images/layerActive.png") no-repeat;
background-size: 100% 100%;
opacity: 1; /* 确保不透明度为1 */
}
.zoom-in {
@ -297,7 +337,7 @@ defineExpose({
cursor: pointer;
}
.zoom-in:hover,
.zoom-in:active {
.zoom-in.active {
background: url("../../../assets/images/zoomOutActive.png") no-repeat;
background-size: 100% 100%;
}
@ -311,7 +351,7 @@ defineExpose({
cursor: pointer;
}
.zoom-out:hover,
.zoom-out:active {
.zoom-out.active {
background: url("../../../assets/images/zoomInActive.png") no-repeat;
background-size: 100% 100%;
}

View File

@ -0,0 +1,677 @@
<template>
<div class="vehicle-movement-control">
<!-- 连接状态指示器 -->
<div class="ws-status" :class="{ connected: wsConnected }">
<span v-if="wsConnected">已连接</span>
<span v-else>未连接</span>
</div>
</div>
<!-- 告警/预警提示框 -->
<!-- <div class="alert-container" v-if="alertMessage"> -->
<div class="alert-container">
<div class="alert-box alert-flash" :class="{'alert-warning': alertType === 'warning', 'alert-danger': alertType === 'alarm'}">
<div class="alert-row">
<img v-if="alertType === 'alarm'" class="alert-icon" :src="alarmIcon" alt="alarm" />
<img v-else-if="alertType === 'warning'" class="alert-icon" :src="warnIcon" alt="warning" />
<span class="alert-title">
{{ alertType === 'alarm' ? '冲突告警' : '冲突预警' }}
</span>
</div>
<div class="alert-content alert-desc">
{{ alertMessage }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { Vector as VectorSource } from 'ol/source';
import { Vector as VectorLayer } from 'ol/layer';
import { Style, Icon } from 'ol/style';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import Overlay from 'ol/Overlay';
import { transform } from 'ol/proj';
// WebSocketService
import WebSocketService, { createWebSocket } from '../../../utils/websocket.js';
// SockJSpolyfill
if (typeof window !== 'undefined' && !window.global) {
window.global = window;
}
//
import carIcon from '../../../assets/images/noPeopleCar.png';
import aircraftIcon from '../../../assets/images/Aircraft.png';
import aircraft1Icon from '../../../assets/images/Aircraft1.png';
import labelBg from '../../../assets/images/label_bg.png';
import airportBg from '../../../assets/images/airport_bg.png';
import airportOutBg from '../../../assets/images/airport_out.png';
import alarmBg from '../../../assets/images/alarm_bg.png';
import warningBg from '../../../assets/images/warning_bg.png';
import alarmIcon from '../../../assets/images/alarm_icon.png';
import warnIcon from '../../../assets/images/warn_icon.png';
// props
const props = defineProps({
map: Object
});
// WebSocket
const wsConnected = ref(false);
let wsService = null;
//
const vehicles = ref({});
let vehicleLayer = null;
let vehicleSource = null;
// /
const alertMessage = ref('与航空器Y117距离小于600m请及时避让');
const alertType = ref('warning'); // 'warning' 'alarm'
let alertTimer = null;
// /
function showAlert(message, type, duration = 5000) {
//
if (alertTimer) {
clearTimeout(alertTimer);
}
//
alertMessage.value = message;
alertType.value = type; // 'warning' 'alarm'
//
alertTimer = setTimeout(() => {
alertMessage.value = '';
alertType.value = '';
}, duration);
}
//
function createVehicleLayer() {
if (!props.map) return;
//
vehicleSource = new VectorSource();
//
vehicleLayer = new VectorLayer({
source: vehicleSource,
zIndex: 20, //
});
//
props.map.addLayer(vehicleLayer);
console.log('车辆移动图层已创建');
//
setupMapListeners();
}
//
function updateVehiclePosition(vehicleData) {
if (!vehicleSource || !props.map) return;
const { object_id, object_type, position, heading, speed } = vehicleData;
console.log(`接收到位置数据: ${object_id}`, position);
let coordinates;
coordinates = transform(
[position.longitude, position.latitude],
'EPSG:4326',
props.map.getView().getProjection()
);
//
let feature = vehicleSource.getFeatureById(object_id);
// ID
// 1.
const isAircraftIn = object_type.toLowerCase().includes('aircraft') && !object_id.toLowerCase().includes('ac001');
const isAircraftOut = object_id.toLowerCase().includes('ac001');
const isAircraft = isAircraftIn || isAircraftOut;
// 2.
const isUnmannedVehicle = object_type === 'UNMANNED_VEHICLE' || object_id.toLowerCase().startsWith('qn');
// 3.
const isSpecialVehicle = object_id.toLowerCase().startsWith('tq');
// 4.
const isShuttleVehicle = object_id.toLowerCase().startsWith('bd');
// -
let iconSrc;
if (isAircraftIn) {
iconSrc = aircraftIcon; //
} else if (isAircraftOut) {
iconSrc = aircraft1Icon; //
} else {
iconSrc = carIcon; //
}
//
const style = new Style({
image: new Icon({
src: iconSrc,
scale: 1.5,
anchor: [0.5, 0.5],
rotation: ((heading - 72) * Math.PI) / 180,
})
});
if (!feature) {
// Feature
feature = new Feature({
geometry: new Point(coordinates),
name: `${object_type} ${object_id}`,
type: object_type,
speed: speed,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
});
feature.setId(object_id);
feature.setStyle(style);
vehicleSource.addFeature(feature);
//
vehicles.value[object_id] = {
id: object_id,
type: object_type,
position: coordinates,
heading: heading,
speed: speed,
feature: feature,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
};
// - 使updateVehicleLabel
updateVehicleLabel(object_id, coordinates, speed);
} else {
// Feature
feature.getGeometry().setCoordinates(coordinates);
feature.setStyle(style);
//
vehicles.value[object_id] = {
...vehicles.value[object_id],
position: coordinates,
heading: heading,
speed: speed,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
};
//
updateVehicleLabel(object_id, coordinates, speed);
}
}
//
function updateVehicleLabel(id, position, speed) {
if (!props.map || !vehicles.value[id]) return;
console.log(`更新标签位置: ${id}, 位置: [${position[0]}, ${position[1]}]`);
// overlay
if (vehicles.value[id].overlay) {
props.map.removeOverlay(vehicles.value[id].overlay);
}
//
const vehicle = vehicles.value[id];
const isAircraftIn = vehicle.isAircraftIn;
const isAircraftOut = vehicle.isAircraftOut;
const isUnmannedVehicle = vehicle.isUnmannedVehicle;
const isSpecialVehicle = vehicle.isSpecialVehicle;
const isShuttleVehicle = vehicle.isShuttleVehicle;
const alarm = vehicle.alarm;
const warning = vehicle.warning;
// -
let backgroundImage;
if (isAircraftOut) {
backgroundImage = airportOutBg; // -
} else if (isAircraftIn) {
backgroundImage = airportBg; // -
}else if(alarm){
backgroundImage = alarmBg; //
} else if(warning){
backgroundImage = warningBg; //
}else {
backgroundImage = labelBg; //
}
//
const labelDiv = document.createElement('div');
labelDiv.className = `vehicle-label ${isAircraftIn ? 'vehicle-aircraft-in' : ''} ${isAircraftOut ? 'vehicle-aircraft-out' : ''} ${isUnmannedVehicle ? 'vehicle-unmanned' : ''} ${isSpecialVehicle ? 'vehicle-special' : ''} ${isShuttleVehicle ? 'vehicle-shuttle' : ''}`;
//
let labelText = '';
labelText = `${id} ${speed.toFixed(2)} km/h`;
labelDiv.innerHTML = labelText;
labelDiv.style.backgroundImage = `url(${backgroundImage})`;
labelDiv.style.backgroundSize = '100% 100%';
labelDiv.style.color = '#fff';
labelDiv.style.padding = '5px 10px';
// Overlay
const overlay = new Overlay({
element: labelDiv,
position: position,
positioning: 'bottom-center', //
offset: [0, -30], //
stopEvent: false,
insertFirst: true, // DOM
autoPan: false, //
});
//
vehicles.value[id].overlay = overlay;
vehicles.value[id].labelDiv = labelDiv;
//
props.map.addOverlay(overlay);
}
//
function setupMapListeners() {
if (!props.map) return;
//
props.map.on('moveend', () => {
//
Object.keys(vehicles.value).forEach(id => {
const vehicle = vehicles.value[id];
if (vehicle.feature) {
const coordinates = vehicle.feature.getGeometry().getCoordinates();
updateVehicleLabel(id, coordinates, vehicle.speed);
}
});
});
}
//
onMounted(() => {
// WebSocket
if (props.map) {
createVehicleLayer();
}
// WebSocket
connectWebSocket();
//
const pingInterval = setInterval(() => {
if (wsService && wsConnected.value) {
sendPing();
}
}, 30000); // 30
//
onUnmounted(() => {
clearInterval(pingInterval);
cleanup();
});
});
// WebSocket
function connectWebSocket() {
try {
// 使WebSocketService
const wsUrl = 'ws://10.0.0.124:8080/collision';
console.log(`正在连接WebSocket: ${wsUrl}`);
wsService = createWebSocket(wsUrl, {
reconnectInterval: 3000,
maxReconnectAttempts: 5
});
//
wsService.on('open', (event) => {
console.log('WebSocket连接成功!');
wsConnected.value = true;
//
setTimeout(() => {
sendSubscribe();
}, 1000); // 1
});
wsService.on('message', (data) => {
handleWsMessage(data);
});
wsService.on('error', (event) => {
console.error('WebSocket错误:', event);
wsConnected.value = false;
});
wsService.on('close', (event) => {
console.log(`WebSocket连接关闭: ${event.code} - ${event.reason}`);
wsConnected.value = false;
});
wsService.on('reconnect_failed', () => {
console.error('WebSocket重连失败已达到最大重试次数');
wsConnected.value = false;
});
} catch (error) {
console.error('创建WebSocket连接失败:', error);
}
}
// WebSocket
function handleWsMessage(message) {
try {
const data = JSON.parse(message);
//
switch (data.type) {
case 'connection':
console.log(`连接确认: ${data.message}`);
break;
case 'position_update':
// payload
if (data.payload && data.payload.object_id) {
updateVehiclePosition(data.payload);
} else {
console.error('位置更新消息格式错误:', data);
}
break;
case 'pong':
console.log('收到心跳响应');
break;
case 'collision_warning':
console.log('收到碰撞预警:', data.payload);
//
if (data.payload) {
// ID
const vehicleId = data.payload.object_id || '未知车辆';
const distance = data.payload.distance || 0;
const message = `预警:${vehicleId} 与其他车辆距离${distance.toFixed(1)}米,请注意避让!`;
showAlert(message, 'warning', 8000);
//
if (vehicles.value[vehicleId]) {
vehicles.value[vehicleId].warning = true;
//
if (vehicles.value[vehicleId].position) {
updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed);
}
}
}
break;
case 'rule_violation':
console.log('收到规则违规:', data.payload);
//
if (data.payload) {
// ID
const vehicleId = data.payload.object_id || '未知车辆';
const violationType = data.payload.violation_type || '未知违规';
const message = `告警:${vehicleId} 发生${violationType},请立即处理!`;
showAlert(message, 'alarm', 10000);
//
if (vehicles.value[vehicleId]) {
vehicles.value[vehicleId].alarm = true;
//
if (vehicles.value[vehicleId].position) {
updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed);
}
}
}
break;
case 'vehicle_command':
console.log('收到车辆控制指令:', data.payload);
break;
default:
//
console.log(`收到其他类型消息: ${data.type}`, data);
break;
}
} catch (e) {
console.error('处理WebSocket消息出错:', e, message);
}
}
//
function sendPing() {
if (wsService) {
wsService.send('ping');
console.log('发送心跳: ping');
}
}
//
function sendSubscribe() {
if (wsService) {
const message = JSON.stringify({
type: 'subscribe',
topics: ['position_update', 'collision_warning', 'rule_violation'],
timestamp: Date.now()
});
wsService.send(message);
console.log('发送订阅请求');
}
}
//
function cleanup() {
// WebSocket
if (wsService) {
wsService.close();
wsService = null;
}
//
if (vehicleLayer && props.map) {
props.map.removeLayer(vehicleLayer);
vehicleLayer = null;
}
//
if (props.map) {
Object.values(vehicles.value).forEach(vehicle => {
if (vehicle.overlay) {
console.log(`移除标签: ${vehicle.id}`);
props.map.removeOverlay(vehicle.overlay);
vehicle.overlay = null;
}
});
}
//
vehicles.value = {};
}
//
watch(() => props.map, (newMap) => {
if (newMap) {
createVehicleLayer();
}
});
//
defineExpose({
updateVehiclePosition,
wsConnected,
sendPing,
sendSubscribe
});
</script>
<style scoped>
.vehicle-movement-control {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1000;
}
.ws-status {
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
background-color: rgba(255, 87, 34, 0.8);
color: white;
transition: background-color 0.3s ease;
text-align: center;
}
.ws-status.connected {
background-color: rgba(76, 175, 80, 0.8);
}
/* 告警/预警容器样式 */
.alert-container {
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
z-index: 2000;
width: 100%;
height: 120px;
max-width: 550px;
text-align: center;
}
.alert-box {
display: flex;
flex-direction: column;
justify-content:center;
align-items: center;
animation: alertSlideIn 1s ease;
position: relative;
box-sizing: border-box;
}
.alert-row {
display: flex;
align-items: center;
/* margin-bottom: 8px; */
}
.alert-icon {
width: 56px;
height: 50px;
margin-right: 12px;
}
.alert-title {
font-size: 26px;
font-weight: bold;
color: #fff;
letter-spacing: 2px;
}
.alert-desc {
font-size: 18px;
color: #fff;
margin-left: 44px;
text-align: left;
}
/* 闪烁动画 */
.alert-flash {
animation: alertFlash 1s steps(2, start) infinite, alertSlideIn 0.5s ease;
}
@keyframes alertFlash {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* 预警样式 - 黄色背景 */
.alert-warning {
width: 100%;
height: 120px;
max-width: 550px;
background: url(../../../assets/images/warn_report.png) no-repeat;
background-size: 100% 100%;
color: #fff;
}
/* 告警样式 - 红色背景 */
.alert-danger {
width: 100%;
height: 120px;
max-width: 550px;
background: url(../../../assets/images/alarm_report.png) no-repeat;
background-size: 100% 100%;
color: #fff;
}
/* 车辆标签样式 */
:deep(.vehicle-label) {
position: absolute;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
transform: translateX(-50%);
text-align: center;
background-repeat: no-repeat;
background-position: center;
min-width: 80px;
min-height: 24px;
display: flex;
align-items: center;
justify-content: center;
z-index: 30; /* 确保标签在图标上方 */
transition: transform 0.3s ease, left 0.3s ease, top 0.3s ease; /* 平滑移动效果 */
will-change: transform, left, top; /* 提示浏览器优化这些属性的变化 */
bottom: 0; /* 确保标签底部对齐定位点 */
}
/* 滑入航空器标签样式 */
:deep(.vehicle-aircraft-in) {
color: #fff;
}
/* 滑出航空器标签样式 */
:deep(.vehicle-aircraft-out) {
color: #333;
}
/* 无人车标签样式 */
:deep(.vehicle-unmanned) {
color: #fff;
}
/* 特勤车标签样式 */
:deep(.vehicle-special) {
color: #fff;
}
/* 摆渡车标签样式 */
:deep(.vehicle-shuttle) {
color: #fff;
}
</style>

View File

@ -1,11 +1,21 @@
<template>
<div class="vehicle-movement-control">
<!-- 连接状态指示器 -->
<div class="ws-status" :class="{ connected: wsConnected }">
<span v-if="wsConnected">已连接</span>
<span v-else>未连接</span>
<!-- 告警/预警提示框 -->
<!-- <div class="alert-container" v-if="alertMessage"> -->
<div class="alert-container">
<div class="alert-box alert-flash" :class="{'alert-warning': alertType === 'warning', 'alert-danger': alertType === 'alarm'}">
<div class="alert-row">
<img v-if="alertType === 'alarm'" class="alert-icon" :src="alarmIcon" alt="alarm" />
<img v-else-if="alertType === 'warning'" class="alert-icon" :src="warnIcon" alt="warning" />
<span class="alert-title">
{{ alertType === 'alarm' ? '冲突告警' : '冲突预警' }}
</span>
</div>
<div class="alert-content alert-desc">
{{ alertMessage }}
</div>
</div>
</div>
</div>
</template>
<script setup>
@ -17,16 +27,14 @@ import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import Overlay from 'ol/Overlay';
import { transform } from 'ol/proj';
// WebSocketService
import WebSocketService, { createWebSocket } from '../../../utils/websocket.js';
// SockJSpolyfill
if (typeof window !== 'undefined' && !window.global) {
window.global = window;
}
let SockJS;
let StompJS;
//
import carIcon from '../../../assets/images/noPeopleCar.png';
import aircraftIcon from '../../../assets/images/Aircraft.png';
@ -34,6 +42,10 @@ import aircraft1Icon from '../../../assets/images/Aircraft1.png';
import labelBg from '../../../assets/images/label_bg.png';
import airportBg from '../../../assets/images/airport_bg.png';
import airportOutBg from '../../../assets/images/airport_out.png';
import alarmBg from '../../../assets/images/alarm_bg.png';
import warningBg from '../../../assets/images/warning_bg.png';
import alarmIcon from '../../../assets/images/alarm_icon.png';
import warnIcon from '../../../assets/images/warn_icon.png';
// props
const props = defineProps({
map: Object
@ -41,13 +53,39 @@ const props = defineProps({
// WebSocket
const wsConnected = ref(false);
let stompClient = null;
let wsService = null;
//
const vehicles = ref({});
let vehicleLayer = null;
let vehicleSource = null;
// /
const vehicleCategories = ref({});
// /
const alertMessage = ref('与航空器Y117距离小于600m请及时避让');
const alertType = ref('warning'); // 'warning' 'alarm'
let alertTimer = null;
// /
function showAlert(message, type, duration = 5000) {
//
if (alertTimer) {
clearTimeout(alertTimer);
}
//
alertMessage.value = message;
alertType.value = type; // 'warning' 'alarm'
//
alertTimer = setTimeout(() => {
alertMessage.value = '';
alertType.value = '';
}, duration);
}
//
function createVehicleLayer() {
if (!props.map) return;
@ -76,28 +114,34 @@ function updateVehiclePosition(vehicleData) {
const { object_id, object_type, position, heading, speed } = vehicleData;
console.log(`接收到位置数据: ${object_id}`, position);
// ()
// WGS84使
let coordinates;
try {
coordinates = transform(
[position.longitude, position.latitude],
'EPSG:4326',
props.map.getView().getProjection()
);
} catch (e) {
console.error('坐标转换失败:', e);
coordinates = [position.x || 0, position.y || 0]; // 使
}
//
let feature = vehicleSource.getFeatureById(object_id);
//
// ID
// 1.
const isAircraftIn = object_type.toLowerCase().includes('aircraft') && !object_id.toLowerCase().includes('ac001');
const isAircraftOut = object_id.toLowerCase().includes('ac001');
const isAircraft = isAircraftIn || isAircraftOut;
// 2.
const isUnmannedVehicle = object_type === 'UNMANNED_VEHICLE' || object_id.toLowerCase().startsWith('qn');
// 3.
const isSpecialVehicle = object_id.toLowerCase().startsWith('tq');
// 4.
const isShuttleVehicle = object_id.toLowerCase().startsWith('bd');
// -
let iconSrc;
if (isAircraftIn) {
@ -108,16 +152,47 @@ function updateVehiclePosition(vehicleData) {
iconSrc = carIcon; //
}
//
const style = new Style({
image: new Icon({
src: iconSrc,
scale: 1.5,
anchor: [0.5, 0.5],
rotation: heading ? (heading * Math.PI / 180) : 0, //
})
});
//
let typeKey = object_type; // 使
//
if (isAircraftIn) {
typeKey = 'AIRCRAFT_IN';
} else if (isAircraftOut) {
typeKey = 'AIRCRAFT_OUT';
} else if (isUnmannedVehicle) {
typeKey = 'UNMANNED_VEHICLE';
} else if (isSpecialVehicle) {
typeKey = 'SPECIAL_VEHICLE';
} else if (isShuttleVehicle) {
typeKey = 'SHUTTLE_VEHICLE';
}
// typeKey
if (!vehicleCategories.value[typeKey]) {
vehicleCategories.value[typeKey] = {
name: isAircraftIn ? '滑入航空器' :
isAircraftOut ? '滑出航空器' :
isUnmannedVehicle ? '无人车' :
isSpecialVehicle ? '特勤车' :
isShuttleVehicle ? '摆渡车' : object_type,
icon: iconSrc,
visible: true,
showLabel: true
};
}
// -
const style = vehicleCategories.value[typeKey].visible ?
new Style({
image: new Icon({
src: iconSrc,
scale: 1.5,
anchor: [0.5, 0.5],
rotation: ((heading - 72) * Math.PI) / 180,
})
}) : new Style({}); // 使null
if (!feature) {
// Feature
feature = new Feature({
@ -127,7 +202,10 @@ function updateVehiclePosition(vehicleData) {
speed: speed,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
});
feature.setId(object_id);
@ -144,15 +222,20 @@ function updateVehiclePosition(vehicleData) {
feature: feature,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
};
// - 使updateVehicleLabel
updateVehicleLabel(object_id, coordinates, speed);
//
if (vehicleCategories.value[typeKey].showLabel) {
updateVehicleLabel(object_id, coordinates, speed);
}
} else {
// Feature
feature.getGeometry().setCoordinates(coordinates);
feature.setStyle(style);
feature.setStyle(style); // null
//
vehicles.value[object_id] = {
@ -162,11 +245,21 @@ function updateVehiclePosition(vehicleData) {
speed: speed,
isAircraftIn: isAircraftIn,
isAircraftOut: isAircraftOut,
isAircraft: isAircraft
isAircraft: isAircraft,
isUnmannedVehicle: isUnmannedVehicle,
isSpecialVehicle: isSpecialVehicle,
isShuttleVehicle: isShuttleVehicle
};
//
updateVehicleLabel(object_id, coordinates, speed);
//
if (vehicleCategories.value[typeKey].showLabel) {
updateVehicleLabel(object_id, coordinates, speed);
} else if (vehicles.value[object_id].overlay) {
// overlay
try {
props.map.removeOverlay(vehicles.value[object_id].overlay);
} catch(e) {}
}
}
}
@ -185,21 +278,42 @@ function updateVehicleLabel(id, position, speed) {
const vehicle = vehicles.value[id];
const isAircraftIn = vehicle.isAircraftIn;
const isAircraftOut = vehicle.isAircraftOut;
const isUnmannedVehicle = vehicle.isUnmannedVehicle;
const isSpecialVehicle = vehicle.isSpecialVehicle;
const isShuttleVehicle = vehicle.isShuttleVehicle;
const alarm = vehicle.alarm;
const warning = vehicle.warning;
// - 使(airport_out.png)使(airport_bg.png)
// key
let typeKey = vehicle.type;
if (isAircraftIn) typeKey = 'AIRCRAFT_IN';
else if (isAircraftOut) typeKey = 'AIRCRAFT_OUT';
else if (isUnmannedVehicle) typeKey = 'UNMANNED_VEHICLE';
else if (isSpecialVehicle) typeKey = 'SPECIAL_VEHICLE';
else if (isShuttleVehicle) typeKey = 'SHUTTLE_VEHICLE';
// -
let backgroundImage;
if (isAircraftOut) {
backgroundImage = airportOutBg; // -
backgroundImage = airportOutBg; // -
} else if (isAircraftIn) {
backgroundImage = airportBg; // -
} else {
backgroundImage = airportBg; // -
}else if(alarm){
backgroundImage = alarmBg; //
} else if(warning){
backgroundImage = warningBg; //
}else {
backgroundImage = labelBg; //
}
//
const labelDiv = document.createElement('div');
labelDiv.className = `vehicle-label ${isAircraftIn ? 'vehicle-aircraft-in' : ''} ${isAircraftOut ? 'vehicle-aircraft-out' : ''} ${(!isAircraftIn && !isAircraftOut) ? 'vehicle-car' : ''}`;
labelDiv.innerHTML = `${id} ${speed} km/h`;
labelDiv.className = `vehicle-label ${isAircraftIn ? 'vehicle-aircraft-in' : ''} ${isAircraftOut ? 'vehicle-aircraft-out' : ''} ${isUnmannedVehicle ? 'vehicle-unmanned' : ''} ${isSpecialVehicle ? 'vehicle-special' : ''} ${isShuttleVehicle ? 'vehicle-shuttle' : ''}`;
//
let labelText = '';
labelText = `${id} ${speed.toFixed(2)} km/h`;
labelDiv.innerHTML = labelText;
labelDiv.style.backgroundImage = `url(${backgroundImage})`;
labelDiv.style.backgroundSize = '100% 100%';
labelDiv.style.color = '#fff';
@ -220,7 +334,7 @@ function updateVehicleLabel(id, position, speed) {
vehicles.value[id].overlay = overlay;
vehicles.value[id].labelDiv = labelDiv;
//
// -
props.map.addOverlay(overlay);
}
@ -241,89 +355,178 @@ function setupMapListeners() {
});
}
//
onMounted(() => {
// WebSocket
if (props.map) {
createVehicleLayer();
}
// WebSocket
connectWebSocket();
//
const pingInterval = setInterval(() => {
if (wsService && wsConnected.value) {
sendPing();
}
}, 30000); // 30
//
onUnmounted(() => {
clearInterval(pingInterval);
cleanup();
});
});
// WebSocket
function connectWebSocket() {
if (!SockJS || !StompJS) {
console.error('SockJS或StompJS库未加载');
return;
}
try {
// 使SockJSSTOMP
const socket = new SockJS('http://10.0.0.124:8081/ws');
stompClient = new StompJS.Client({
webSocketFactory: () => socket,
connectHeaders: {},
debug: function(str) {
console.log('STOMP Debug: ' + str);
},
reconnectDelay: 3000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
// 使WebSocketService
const wsUrl = 'ws://10.0.0.124:8080/collision';
console.log(`正在连接WebSocket: ${wsUrl}`);
wsService = createWebSocket(wsUrl, {
reconnectInterval: 3000,
maxReconnectAttempts: 5
});
//
stompClient.onConnect = (frame) => {
console.log('WebSocket连接成功');
//
wsService.on('open', (event) => {
console.log('WebSocket连接成功!');
wsConnected.value = true;
//
stompClient.subscribe('/topic/realtime', (message) => {
handleWsMessage(message.body);
});
};
//
setTimeout(() => {
sendSubscribe();
}, 1000); // 1
});
stompClient.onStompError = (frame) => {
console.error('STOMP错误:', frame.headers['message']);
console.error('详细信息:', frame.body);
wsService.on('message', (data) => {
handleWsMessage(data);
});
wsService.on('error', (event) => {
console.error('WebSocket错误:', event);
wsConnected.value = false;
};
});
stompClient.onWebSocketClose = () => {
console.log('WebSocket连接关闭');
wsService.on('close', (event) => {
console.log(`WebSocket连接关闭: ${event.code} - ${event.reason}`);
wsConnected.value = false;
};
});
stompClient.onWebSocketError = (error) => {
console.error('WebSocket错误:', error);
wsService.on('reconnect_failed', () => {
console.error('WebSocket重连失败,已达到最大重试次数');
wsConnected.value = false;
};
});
//
stompClient.activate();
} catch (error) {
console.error('创建WebSocket连接失败:', error);
}
}
// WebSocket
// WebSocket
function handleWsMessage(message) {
try {
const data = JSON.parse(message);
//
switch (data.type) {
case 'connection':
console.log(`连接确认: ${data.message}`);
break;
case 'position_update':
updateVehiclePosition(data.payload);
// payload
if (data.payload && data.payload.object_id) {
updateVehiclePosition(data.payload);
} else {
console.error('位置更新消息格式错误:', data);
}
break;
case 'pong':
console.log('收到心跳响应');
break;
case 'collision_warning':
console.log('收到碰撞预警:', data.payload);
//
if (data.payload) {
// ID
const vehicleId = data.payload.object_id || '未知车辆';
const distance = data.payload.distance || 0;
const message = `预警:${vehicleId} 与其他车辆距离${distance.toFixed(1)}米,请注意避让!`;
showAlert(message, 'warning', 8000);
//
if (vehicles.value[vehicleId]) {
vehicles.value[vehicleId].warning = true;
//
if (vehicles.value[vehicleId].position) {
updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed);
}
}
}
break;
case 'rule_violation':
console.log('收到规则违规:', data.payload);
//
if (data.payload) {
// ID
const vehicleId = data.payload.object_id || '未知车辆';
const violationType = data.payload.violation_type || '未知违规';
const message = `告警:${vehicleId} 发生${violationType},请立即处理!`;
showAlert(message, 'alarm', 10000);
//
if (vehicles.value[vehicleId]) {
vehicles.value[vehicleId].alarm = true;
//
if (vehicles.value[vehicleId].position) {
updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed);
}
}
}
break;
case 'vehicle_command':
console.log('收到车辆控制指令:', data.payload);
break;
default:
//
console.log(`收到其他类型消息: ${data.type}`, data);
break;
}
} catch (e) {
console.error('处理WebSocket消息出错:', e);
console.error('处理WebSocket消息出错:', e, message);
}
}
//
function sendPing() {
if (wsService) {
wsService.send('ping');
console.log('发送心跳: ping');
}
}
//
function sendSubscribe() {
if (wsService) {
const message = JSON.stringify({
type: 'subscribe',
topics: ['position_update', 'collision_warning', 'rule_violation'],
timestamp: Date.now()
});
wsService.send(message);
console.log('发送订阅请求');
}
}
//
function cleanup() {
// WebSocket
if (stompClient) {
stompClient.deactivate();
stompClient = null;
if (wsService) {
wsService.close();
wsService = null;
}
//
@ -354,67 +557,147 @@ watch(() => props.map, (newMap) => {
}
});
//
onMounted(async () => {
// SockJSStompJS
try {
// 使
if (window.SockJS) {
SockJS = window.SockJS;
} else {
//
const sockjsModule = await import('sockjs-client');
SockJS = sockjsModule.default;
}
if (window.StompJs) {
StompJS = window.StompJs;
} else {
const stompModule = await import('@stomp/stompjs');
StompJS = stompModule;
}
// WebSocket
if (props.map) {
createVehicleLayer();
}
connectWebSocket();
} catch (error) {
console.error('加载WebSocket库失败:', error);
}
});
//
onUnmounted(() => {
cleanup();
});
//
defineExpose({
updateVehiclePosition,
wsConnected
wsConnected,
sendPing,
sendSubscribe,
vehicleCategories,
setCategoryVisibility(type, { visible, showLabel }) {
if (vehicleCategories.value[type]) {
//
vehicleCategories.value[type].visible = visible;
vehicleCategories.value[type].showLabel = showLabel;
// -
Object.values(vehicles.value).forEach(vehicle => {
let vehicleTypeKey = vehicle.type;
if (vehicle.isAircraftIn) vehicleTypeKey = 'AIRCRAFT_IN';
else if (vehicle.isAircraftOut) vehicleTypeKey = 'AIRCRAFT_OUT';
else if (vehicle.isUnmannedVehicle) vehicleTypeKey = 'UNMANNED_VEHICLE';
else if (vehicle.isSpecialVehicle) vehicleTypeKey = 'SPECIAL_VEHICLE';
else if (vehicle.isShuttleVehicle) vehicleTypeKey = 'SHUTTLE_VEHICLE';
//
if (vehicleTypeKey === type) {
// -
if (vehicle.feature) {
if (visible) {
//
vehicle.feature.setStyle(
new Style({
image: new Icon({
src: vehicleCategories.value[type].icon,
scale: 1.5,
anchor: [0.5, 0.5],
rotation: ((vehicle.heading - 72) * Math.PI) / 180,
})
})
);
} else {
// 使null
vehicle.feature.setStyle(new Style({}));
}
}
//
if (vehicle.overlay) {
if (showLabel) {
try { props.map.removeOverlay(vehicle.overlay); } catch (e) {}
props.map.addOverlay(vehicle.overlay);
} else {
try { props.map.removeOverlay(vehicle.overlay); } catch (e) {}
}
}
}
});
}
}
});
</script>
<style scoped>
.vehicle-movement-control {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1000;
/* 告警/预警容器样式 */
.alert-container {
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
z-index: 2000;
width: 100%;
height: 120px;
max-width: 550px;
text-align: center;
}
.ws-status {
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
background-color: rgba(255, 87, 34, 0.8);
color: white;
transition: background-color 0.3s ease;
.alert-box {
display: flex;
flex-direction: column;
justify-content:center;
align-items: center;
animation: alertSlideIn 1s ease;
position: relative;
box-sizing: border-box;
}
.ws-status.connected {
background-color: rgba(76, 175, 80, 0.8);
.alert-row {
display: flex;
align-items: center;
/* margin-bottom: 8px; */
}
.alert-icon {
width: 56px;
height: 50px;
margin-right: 12px;
}
.alert-title {
font-size: 26px;
font-weight: bold;
color: #fff;
letter-spacing: 2px;
}
.alert-desc {
font-size: 18px;
color: #fff;
margin-left: 44px;
text-align: left;
}
/* 闪烁动画 */
.alert-flash {
animation: alertFlash 1s steps(2, start) infinite, alertSlideIn 0.5s ease;
}
@keyframes alertFlash {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* 预警样式 - 黄色背景 */
.alert-warning {
width: 100%;
height: 120px;
max-width: 550px;
background: url(../../../assets/images/warn_report.png) no-repeat;
background-size: 100% 100%;
color: #fff;
}
/* 告警样式 - 红色背景 */
.alert-danger {
width: 100%;
height: 120px;
max-width: 550px;
background: url(../../../assets/images/alarm_report.png) no-repeat;
background-size: 100% 100%;
color: #fff;
}
/* 车辆标签样式 */
@ -440,15 +723,28 @@ defineExpose({
bottom: 0; /* 确保标签底部对齐定位点 */
}
/* 滑入航空器标签样式 */
:deep(.vehicle-aircraft-in) {
/* 滑入航空器标签特殊样式,如果需要 */
color: #fff;
}
/* 滑出航空器标签样式 */
:deep(.vehicle-aircraft-out) {
/* 滑出航空器标签特殊样式,如果需要 */
color: #333;
}
:deep(.vehicle-car) {
/* 车辆标签特殊样式,如果需要 */
/* 无人车标签样式 */
:deep(.vehicle-unmanned) {
color: #fff;
}
/* 特勤车标签样式 */
:deep(.vehicle-special) {
color: #fff;
}
/* 摆渡车标签样式 */
:deep(.vehicle-shuttle) {
color: #fff;
}
</style>

View File

@ -1,6 +1,11 @@
<template>
<div class="platform-overview platform-no-padding">
<OpenLayersMap ref="mapRef" />
<OpenLayersMap
ref="mapRef"
:vehicleMovementControl="vehicleMovementRef"
:vehicleCategories="vehicleCategories"
@setCategoryVisibility="handleSetCategoryVisibility"
/>
<CarAlarm />
<!-- 报警通知面板 -->
<AlarmNotification v-if="showAlarmPanel" @close="showAlarmPanel = false" />
@ -41,7 +46,7 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
import OpenLayersMap from '../../components/map/OpenLayersMap.vue';
import CarAlarm from '../../components/map/info/carClarm.vue';
import Eventlist from '../../components/map/info/eventlist.vue';
@ -57,6 +62,18 @@ const mapRef = ref(null);
const vehicleMovementRef = ref(null);
const map = ref(null); //
// VehicleMovementControl
const vehicleCategories = computed(() => {
return vehicleMovementRef.value?.vehicleCategories || {};
});
//
function handleSetCategoryVisibility(type, settings) {
if (vehicleMovementRef.value && vehicleMovementRef.value.setCategoryVisibility) {
vehicleMovementRef.value.setCategoryVisibility(type, settings);
}
}
//
const isDevelopment = process.env.NODE_ENV === 'development';