图层使用websocke动态数据
This commit is contained in:
parent
a9c66a376a
commit
921c2e8e5d
BIN
src/assets/images/alarm_bg.png
Normal file
BIN
src/assets/images/alarm_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 597 B |
BIN
src/assets/images/alarm_icon.png
Normal file
BIN
src/assets/images/alarm_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/assets/images/alarm_report.png
Normal file
BIN
src/assets/images/alarm_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
BIN
src/assets/images/warn_icon.png
Normal file
BIN
src/assets/images/warn_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
BIN
src/assets/images/warn_report.png
Normal file
BIN
src/assets/images/warn_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
src/assets/images/warning_bg.png
Normal file
BIN
src/assets/images/warning_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 571 B |
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
|
||||
// 只在showLabel为true且图层可见时addOverlay
|
||||
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);
|
||||
|
||||
// 遍历该图层的所有feature,更新其overlay的可见性
|
||||
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>
|
||||
@ -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;
|
||||
|
||||
// 只在showLabel为true且图层可见时addOverlay
|
||||
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);
|
||||
|
||||
// 遍历该图层的所有feature,更新其overlay的可见性
|
||||
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>
|
||||
|
||||
@ -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);
|
||||
|
||||
// 如果props中有vehicleMovementControl,直接调用其方法
|
||||
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%;
|
||||
}
|
||||
|
||||
677
src/components/map/controls/VehicleMovementControl copy.vue
Normal file
677
src/components/map/controls/VehicleMovementControl copy.vue
Normal 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';
|
||||
|
||||
// 为SockJS提供polyfill
|
||||
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>
|
||||
@ -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';
|
||||
|
||||
// 为SockJS提供polyfill
|
||||
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 {
|
||||
// 使用SockJS和STOMP客户端创建连接
|
||||
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 () => {
|
||||
// 动态加载SockJS和StompJS
|
||||
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>
|
||||
@ -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';
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user