1097 lines
38 KiB
Vue
1097 lines
38 KiB
Vue
<template>
|
||
<!-- 气象监测站弹窗 -->
|
||
<WeatherStationPopup
|
||
:visible="weatherStationVisible"
|
||
@close="weatherStationVisible = false"
|
||
/>
|
||
|
||
<!-- 车辆详情弹窗 -->
|
||
<VehicleDetailPopup
|
||
:visible="vehicleDetailVisible"
|
||
:detail="vehicleDetail"
|
||
:popup-style="detailPopupStyle"
|
||
@close="vehicleDetailVisible = false"
|
||
/>
|
||
|
||
<!-- 告警/预警提示框 -->
|
||
<!-- <AlertNotificationSystem
|
||
:alert-message="alertMessage"
|
||
:alert-type="alertType"
|
||
@show="handleAlertShow"
|
||
/>
|
||
-->
|
||
<!-- 车辆动画系统 -->
|
||
<VehicleAnimationSystem
|
||
ref="animationSystem"
|
||
:map="map"
|
||
:vehicle-source="vehicleSource"
|
||
:vehicles="vehicles"
|
||
:get-vehicle-style="styleManager?.value?.getVehicleStyle || defaultGetVehicleStyle"
|
||
/>
|
||
|
||
<!-- 车辆标签系统 -->
|
||
<VehicleLabelSystem
|
||
ref="labelSystem"
|
||
:map="map"
|
||
:vehicles="vehicles"
|
||
/>
|
||
|
||
<!-- 车辆样式管理器 -->
|
||
<VehicleStyleManager
|
||
ref="styleManager"
|
||
:vehicles="vehicles"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted, watch, onActivated, onDeactivated } 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 { transform } from 'ol/proj';
|
||
import WebSocketService, { createWebSocket, resetWebSocketInstance } from '../../../utils/websocket.js';
|
||
|
||
// 导入子组件
|
||
import VehicleAnimationSystem from './VehicleAnimationSystem.vue';
|
||
import VehicleLabelSystem from './VehicleLabelSystem.vue';
|
||
import VehicleDetailPopup from './VehicleDetailPopup.vue';
|
||
import WeatherStationPopup from './WeatherStationPopup.vue';
|
||
// import AlertNotificationSystem from './AlertNotificationSystem.vue';
|
||
import VehicleStyleManager from './VehicleStyleManager.vue';
|
||
|
||
// 导入默认图标
|
||
import carIcon from '../../../assets/images/noPeopleCar.png';
|
||
import aircraftInIcon from '../../../assets/images/Aircraft1.png'; // 滑入航空器使用黄色图标
|
||
import aircraftOutIcon from '../../../assets/images/Aircraft.png'; // 滑出航空器使用蓝色图标
|
||
|
||
// 为SockJS提供polyfill
|
||
if (typeof window !== 'undefined' && !window.global) {
|
||
window.global = window;
|
||
}
|
||
|
||
// 定义props接收地图实例
|
||
const props = defineProps({
|
||
map: Object
|
||
});
|
||
|
||
// 子组件引用
|
||
const animationSystem = ref(null);
|
||
const labelSystem = ref(null);
|
||
const styleManager = ref(null);
|
||
|
||
// 气象监测站数据
|
||
const weatherStationVisible = ref(true);
|
||
|
||
// 车辆详情数据
|
||
const vehicleDetailVisible = ref(false);
|
||
const vehicleDetail = ref({
|
||
id: 'QN001',
|
||
type: '驱鸟车',
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: 'A区T3点',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
});
|
||
|
||
// 弹窗位置样式
|
||
const detailPopupStyle = ref({
|
||
left: '0px',
|
||
top: '0px'
|
||
});
|
||
|
||
// WebSocket连接状态
|
||
const wsConnected = ref(false);
|
||
let wsService = null;
|
||
let reconnectTimer = null;
|
||
|
||
// 车辆数据存储
|
||
const vehicles = ref({});
|
||
let vehicleLayer = null;
|
||
let vehicleSource = null;
|
||
|
||
// 车辆超速状态超时计时器存储
|
||
const speedViolationTimers = {};
|
||
|
||
// 动态收集所有车辆/航空器类别
|
||
const vehicleCategories = ref({
|
||
'AIRCRAFT_IN': { visible: true, showLabel: true, name: '滑入航空器' },
|
||
'AIRCRAFT_OUT': { visible: true, showLabel: true, name: '滑出航空器' },
|
||
'AIRCRAFT': { visible: true, showLabel: true, name: '其他航空器' },
|
||
'UNMANNED_VEHICLE': { visible: true, showLabel: true, name: '无人车' },
|
||
'AIRPORT_VEHICLE': { visible: true, showLabel: true, name: '特勤车' },
|
||
'SHUTTLE_VEHICLE': { visible: true, showLabel: true, name: '摆渡车' }
|
||
});
|
||
|
||
// 告警/预警状态
|
||
const alertMessage = ref('');
|
||
const alertType = ref('');
|
||
let alertTimer = null;
|
||
|
||
// 违规状态计时器
|
||
const violationStatusTimers = ref({});
|
||
|
||
// 处理告警显示
|
||
function handleAlertShow({ message, type, duration = 5000 }) {
|
||
if (alertTimer) {
|
||
clearTimeout(alertTimer);
|
||
}
|
||
|
||
alertMessage.value = message;
|
||
alertType.value = type;
|
||
|
||
alertTimer = setTimeout(() => {
|
||
alertMessage.value = '';
|
||
alertType.value = '';
|
||
}, duration);
|
||
}
|
||
|
||
// 显示告警/预警消息
|
||
function showAlert(message, type, duration = 5000) {
|
||
handleAlertShow({ message, type, duration });
|
||
}
|
||
|
||
// 创建车辆图层
|
||
function createVehicleLayer() {
|
||
if (!props.map) return;
|
||
|
||
vehicleSource = new VectorSource();
|
||
|
||
vehicleLayer = new VectorLayer({
|
||
source: vehicleSource,
|
||
zIndex: 20,
|
||
});
|
||
|
||
props.map.addLayer(vehicleLayer);
|
||
|
||
setupMapListeners();
|
||
|
||
props.map.on('click', handleMapClick);
|
||
}
|
||
|
||
// 处理地图点击事件
|
||
function handleMapClick(event) {
|
||
const feature = props.map.forEachFeatureAtPixel(event.pixel, function(feature) {
|
||
return feature;
|
||
});
|
||
|
||
if (feature) {
|
||
const objectId = feature.getId();
|
||
if (objectId && vehicles.value[objectId]) {
|
||
const vehicle = vehicles.value[objectId];
|
||
|
||
vehicleDetail.value = {
|
||
id: objectId,
|
||
type: vehicle.type,
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: '当前位置',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
};
|
||
|
||
const pixel = event.pixel;
|
||
const viewportPosition = props.map.getViewport().getBoundingClientRect();
|
||
|
||
detailPopupStyle.value = {
|
||
left: (viewportPosition.left + pixel[0] + 10) + 'px',
|
||
top: (viewportPosition.top + pixel[1] + 10) + 'px'
|
||
};
|
||
|
||
vehicleDetailVisible.value = true;
|
||
} else {
|
||
vehicleDetailVisible.value = false;
|
||
}
|
||
} else {
|
||
vehicleDetailVisible.value = false;
|
||
}
|
||
}
|
||
|
||
// 监听地图变化,确保标签位置正确更新
|
||
function setupMapListeners() {
|
||
if (!props.map) return;
|
||
|
||
// 监听地图移动结束事件
|
||
props.map.on('moveend', () => {
|
||
if (labelSystem.value) {
|
||
labelSystem.value.updateAllLabels();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 更新车辆位置
|
||
function updateVehiclePosition(vehicleData) {
|
||
if (!vehicleSource || !props.map) return;
|
||
|
||
const { object_id, object_type, position, heading, speed } = vehicleData;
|
||
|
||
let coordinates;
|
||
|
||
coordinates = transform(
|
||
[position.longitude, position.latitude],
|
||
'EPSG:4326',
|
||
props.map.getView().getProjection()
|
||
);
|
||
|
||
let feature = vehicleSource.getFeatureById(object_id);
|
||
|
||
// 根据object_type正确分类车辆
|
||
let vehicleType = object_type.toUpperCase();
|
||
|
||
// 判断航空器类型
|
||
// CA开头的航班是滑入航空器,使用黄色Aircraft1.png图标和airport_out.png文本背景
|
||
// MU开头的航班是滑出航空器,使用蓝色Aircraft.png图标和airport_bg.png文本背景
|
||
const isAircraftOut = vehicleType === 'AIRCRAFT' && object_id.toLowerCase().includes('ca');
|
||
const isAircraftIn = vehicleType === 'AIRCRAFT' && object_id.toLowerCase().includes('mu');
|
||
const isAircraft = vehicleType === 'AIRCRAFT';
|
||
|
||
// 判断地面车辆类型
|
||
const isUnmannedVehicle = vehicleType === 'UNMANNED_VEHICLE'; // 无人车,使用noPeopleCar.png图标和label_bg.png文本背景
|
||
const isSpecialVehicle = vehicleType === 'AIRPORT_VEHICLE'; // 特勤车
|
||
const isShuttleVehicle = vehicleType === 'SHUTTLE_VEHICLE'; // 摆渡车
|
||
|
||
if (!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);
|
||
// 使用默认样式,确保styleManager.value存在时才使用它的方法
|
||
if (styleManager.value) {
|
||
feature.setStyle(styleManager.value.getVehicleStyle(object_id, speed, heading));
|
||
} else {
|
||
// 提供一个默认样式,根据车辆类型选择图标
|
||
feature.setStyle(defaultGetVehicleStyle(object_id, speed, heading));
|
||
}
|
||
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
|
||
};
|
||
|
||
// 初始化动画数据
|
||
if (animationSystem.value) {
|
||
animationSystem.value.initVehicleAnimation(object_id, coordinates, heading, speed);
|
||
}
|
||
|
||
// 创建标签
|
||
if (labelSystem.value) {
|
||
labelSystem.value.updateVehicleLabel(object_id, coordinates, speed);
|
||
}
|
||
} else {
|
||
// 更新动画目标
|
||
if (animationSystem.value) {
|
||
animationSystem.value.updateVehicleAnimationTarget(object_id, coordinates, heading, speed);
|
||
}
|
||
|
||
vehicles.value[object_id] = {
|
||
...vehicles.value[object_id],
|
||
position: coordinates,
|
||
heading: heading,
|
||
speed: speed
|
||
};
|
||
|
||
// 更新标签位置,确保标签始终跟随图标
|
||
// 但是如果车辆处于超速状态,不在这里更新标签,避免与超速状态标签冲突
|
||
if (labelSystem.value && !vehicles.value[object_id].speedViolation) {
|
||
labelSystem.value.updateVehicleLabel(object_id, coordinates, speed);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 连接WebSocket
|
||
function connectWebSocket() {
|
||
// 重置WebSocket实例以强制创建新实例
|
||
resetWebSocketInstance();
|
||
|
||
try {
|
||
// 使用WebSocketService创建连接
|
||
const wsUrl = import.meta.env.VITE_APP_WEBSOCKET_URL;
|
||
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) {
|
||
// 检查是否需要清除车辆的告警状态
|
||
clearVehicleAlertStatus(data.payload);
|
||
|
||
// 更新车辆位置
|
||
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) {
|
||
labelSystem.value.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 || data.payload.vehicleId || data.payload.vehicleLicense || '未知车辆';
|
||
const violationType = data.payload.violationType || '未知违规';
|
||
const alertLevel = data.payload.alertLevel || 'INFO';
|
||
const description = data.payload.description || '';
|
||
const limitValue = data.payload.limitValue;
|
||
const actualValue = data.payload.actualValue;
|
||
const ruleName = data.payload.ruleName || '交通规则';
|
||
|
||
// 检查是否为超速违规
|
||
const isSpeedViolation = violationType === 'SPEED_VIOLATION';
|
||
// 检查是否为超速结束
|
||
const isSpeedViolationEnd = violationType === 'SPEED_VIOLATION_END';
|
||
|
||
// 如果是超速违规,特殊处理
|
||
if (isSpeedViolation && alertLevel === 'WARNING') {
|
||
console.log(`检测到超速违规: ${vehicleId}, 实际速度: ${actualValue}, 限速: ${limitValue}`);
|
||
|
||
// 在地图上标记该车辆的超速状态
|
||
if (vehicles.value[vehicleId]) {
|
||
// 避免重复设置超速状态导致闪烁
|
||
const alreadyInSpeedViolation = vehicles.value[vehicleId].speedViolation;
|
||
|
||
// 强制设置超速状态,确保图标正确显示
|
||
vehicles.value[vehicleId].info = false;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].warning = false; // 清除warning状态,使用speedViolation状态
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].speedViolation = true; // 标记为超速状态
|
||
vehicles.value[vehicleId].limitValue = limitValue;
|
||
vehicles.value[vehicleId].actualValue = actualValue;
|
||
vehicles.value[vehicleId].description = description;
|
||
vehicles.value[vehicleId].ruleName = ruleName;
|
||
vehicles.value[vehicleId].lastSpeedViolationTime = Date.now(); // 记录最后一次超速时间
|
||
|
||
// 设置状态锁定,确保一段时间内不会改变状态
|
||
if (!vehicles.value[vehicleId].statusLock) {
|
||
vehicles.value[vehicleId].statusLock = {
|
||
active: false,
|
||
type: null,
|
||
until: 0
|
||
};
|
||
}
|
||
|
||
// 锁定超速状态20秒,确保图标不会来回切换
|
||
vehicles.value[vehicleId].statusLock = {
|
||
active: true,
|
||
type: 'speedViolation',
|
||
until: Date.now() + 20000 // 20秒锁定
|
||
};
|
||
|
||
// 清除任何可能存在的缓存图标,确保使用警告图标
|
||
vehicles.value[vehicleId].cachedIconSrc = null;
|
||
|
||
// 更新车辆图标为超速警告图标(只在首次设置或状态改变时更新)
|
||
if (!alreadyInSpeedViolation) {
|
||
if (vehicles.value[vehicleId].feature) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
vehicles.value[vehicleId].lastIconUpdateTime = Date.now();
|
||
}
|
||
}
|
||
|
||
// 更新标签显示为超速状态(只显示超速相关信息)
|
||
if (vehicles.value[vehicleId].position && labelSystem.value) {
|
||
// 先移除可能存在的旧标签,避免重复显示
|
||
labelSystem.value.removeVehicleLabel(vehicleId);
|
||
|
||
// 然后创建新的超速状态标签
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, actualValue || vehicles.value[vehicleId].speed, {
|
||
description: description || `超速违规`,
|
||
limitValue: limitValue,
|
||
actualValue: actualValue,
|
||
ruleName: ruleName,
|
||
isSpeedViolation: true // 标记为超速状态,避免显示默认信息
|
||
});
|
||
}
|
||
|
||
// 设置或重置超速状态自动清除计时器
|
||
if (speedViolationTimers[vehicleId]) {
|
||
clearTimeout(speedViolationTimers[vehicleId]);
|
||
}
|
||
|
||
// 设置新的超时计时器,如果在一定时间内(如20秒)没有再次接收到超速消息,则自动清除超速状态
|
||
speedViolationTimers[vehicleId] = setTimeout(() => {
|
||
// 确保车辆仍然存在
|
||
if (vehicles.value[vehicleId]) {
|
||
console.log(`超速状态超时: ${vehicleId}, 自动清除超速状态`);
|
||
|
||
// 检查是否可以清除状态锁定
|
||
if (!vehicles.value[vehicleId].statusLock ||
|
||
!vehicles.value[vehicleId].statusLock.active ||
|
||
Date.now() > vehicles.value[vehicleId].statusLock.until) {
|
||
|
||
// 清除超速状态
|
||
clearSpeedViolationStatus(vehicleId);
|
||
|
||
// 清除计时器引用
|
||
delete speedViolationTimers[vehicleId];
|
||
} else {
|
||
// 状态仍然锁定,延迟清除
|
||
console.log(`车辆${vehicleId}状态仍然锁定,延迟清除超速状态`);
|
||
|
||
// 重新设置计时器,等待锁定结束
|
||
speedViolationTimers[vehicleId] = setTimeout(() => {
|
||
clearSpeedViolationStatus(vehicleId);
|
||
delete speedViolationTimers[vehicleId];
|
||
}, vehicles.value[vehicleId].statusLock.until - Date.now());
|
||
}
|
||
}
|
||
}, 20000); // 20秒超时,确保状态稳定
|
||
}
|
||
return; // 已处理超速情况,不再进入下面的alertLevel处理
|
||
}
|
||
|
||
// 如果是超速结束,清除超速状态
|
||
if (isSpeedViolationEnd) {
|
||
console.log(`检测到超速结束: ${vehicleId}`);
|
||
|
||
// 在地图上清除该车辆的超速状态
|
||
if (vehicles.value[vehicleId]) {
|
||
// 检查是否可以清除状态锁定
|
||
if (!vehicles.value[vehicleId].statusLock ||
|
||
!vehicles.value[vehicleId].statusLock.active ||
|
||
Date.now() > vehicles.value[vehicleId].statusLock.until) {
|
||
|
||
// 清除超速状态,使用专门的函数
|
||
clearSpeedViolationStatus(vehicleId);
|
||
} else {
|
||
// 状态仍然锁定,延迟清除
|
||
console.log(`车辆${vehicleId}状态仍然锁定,延迟清除超速状态`);
|
||
|
||
// 设置计时器,等待锁定结束
|
||
if (speedViolationTimers[vehicleId]) {
|
||
clearTimeout(speedViolationTimers[vehicleId]);
|
||
}
|
||
|
||
speedViolationTimers[vehicleId] = setTimeout(() => {
|
||
clearSpeedViolationStatus(vehicleId);
|
||
delete speedViolationTimers[vehicleId];
|
||
}, vehicles.value[vehicleId].statusLock.until - Date.now());
|
||
}
|
||
}
|
||
return; // 已处理超速结束情况,不再进入下面的alertLevel处理
|
||
}
|
||
|
||
// 根据alertLevel处理其他类型的告警
|
||
switch (alertLevel.toUpperCase()) {
|
||
case 'Info': // 预警
|
||
// 显示大的预警提示框
|
||
showAlert(`预警:${vehicleId} ${description}`, 'warning', 10000);
|
||
|
||
// 在地图上标记该车辆的预警状态
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].warning = true;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
|
||
// 更新车辆图标为警告图标
|
||
if (vehicles.value[vehicleId].feature) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
}
|
||
|
||
// 更新标签显示
|
||
if (vehicles.value[vehicleId].position) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: description,
|
||
limitValue: limitValue,
|
||
actualValue: actualValue
|
||
});
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'ALERT': // 告警
|
||
// 显示大的告警提示框
|
||
showAlert(`⚠️ 告警:${vehicleId} ${description}`, 'alarm', 10000);
|
||
|
||
// 在地图上标记该车辆的告警状态
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].alarm = true;
|
||
vehicles.value[vehicleId].warning = false;
|
||
vehicles.value[vehicleId].critical = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
|
||
// 更新车辆图标为告警图标
|
||
if (vehicles.value[vehicleId].feature) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
}
|
||
|
||
// 更新标签显示
|
||
if (vehicles.value[vehicleId].position) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: description,
|
||
limitValue: limitValue,
|
||
actualValue: actualValue
|
||
});
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'CRITICAL': // 越界
|
||
// 不显示大的提示框,仅改变车辆图标颜色和标签背景
|
||
if (vehicles.value[vehicleId]) {
|
||
vehicles.value[vehicleId].critical = true;
|
||
vehicles.value[vehicleId].alarm = false;
|
||
vehicles.value[vehicleId].warning = false;
|
||
vehicles.value[vehicleId].info = false;
|
||
|
||
// 更新车辆图标为警告图标
|
||
if (vehicles.value[vehicleId].feature) {
|
||
vehicles.value[vehicleId].feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicles.value[vehicleId].speed, vehicles.value[vehicleId].heading));
|
||
}
|
||
|
||
// 更新标签显示
|
||
if (vehicles.value[vehicleId].position) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicles.value[vehicleId].position, vehicles.value[vehicleId].speed, {
|
||
description: description,
|
||
limitValue: limitValue,
|
||
actualValue: actualValue
|
||
});
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'WARNING': // 超速 - 这个case已经在上面的SPEED_VIOLATION中处理了,这里不再重复处理
|
||
console.log(`收到WARNING级别的超速消息,但已在SPEED_VIOLATION中处理: ${vehicleId}`);
|
||
break;
|
||
|
||
default:
|
||
console.log(`未处理的告警级别: ${alertLevel}`);
|
||
}
|
||
}
|
||
break;
|
||
case 'vehicle_command':
|
||
console.log('收到车辆控制指令:', data.payload);
|
||
break;
|
||
default:
|
||
// 其他类型的消息可以根据需要处理
|
||
console.log(`收到其他类型消息: ${data.type}`, data);
|
||
break;
|
||
}
|
||
} catch (e) {
|
||
console.error('处理WebSocket消息出错:', e, message);
|
||
}
|
||
}
|
||
|
||
// 清除车辆超速状态的专门函数
|
||
function clearSpeedViolationStatus(vehicleId) {
|
||
const vehicle = vehicles.value[vehicleId];
|
||
if (!vehicle) return;
|
||
|
||
console.log(`开始清除车辆${vehicleId}的超速状态`);
|
||
|
||
// 检查是否可以清除状态锁定
|
||
if (vehicle.statusLock && vehicle.statusLock.active && Date.now() < vehicle.statusLock.until) {
|
||
console.log(`车辆${vehicleId}状态锁定中,暂不清除超速状态`);
|
||
return;
|
||
}
|
||
|
||
// 清除所有告警状态
|
||
vehicle.info = false;
|
||
vehicle.alarm = false;
|
||
vehicle.warning = false;
|
||
vehicle.critical = false;
|
||
vehicle.speedViolation = false;
|
||
vehicle.limitValue = undefined;
|
||
vehicle.actualValue = undefined;
|
||
vehicle.description = undefined;
|
||
vehicle.ruleName = undefined;
|
||
|
||
// 清除状态锁定
|
||
if (vehicle.statusLock) {
|
||
vehicle.statusLock.active = false;
|
||
vehicle.statusLock.type = null;
|
||
vehicle.statusLock.until = 0;
|
||
}
|
||
|
||
// 清除缓存的图标,确保使用默认图标
|
||
vehicle.cachedIconSrc = null;
|
||
vehicle.lastIconUpdateTime = Date.now();
|
||
|
||
// 更新车辆图标为默认图标
|
||
if (vehicle.feature && styleManager.value) {
|
||
vehicle.feature.setStyle(styleManager.value.getVehicleStyle(vehicleId, vehicle.speed, vehicle.heading));
|
||
}
|
||
|
||
// 更新标签显示为正常状态(只显示车辆ID,不显示0.00km/h)
|
||
if (vehicle.position && labelSystem.value) {
|
||
labelSystem.value.updateVehicleLabel(vehicleId, vehicle.position, vehicle.speed > 0.1 ? vehicle.speed : 0);
|
||
}
|
||
|
||
console.log(`已成功清除车辆${vehicleId}的超速状态`);
|
||
}
|
||
|
||
// 清除车辆告警状态(如果需要)
|
||
function clearVehicleAlertStatus(vehicleData) {
|
||
const { object_id, speed } = vehicleData;
|
||
|
||
// 检查该车辆是否已存在且有告警状态
|
||
const existingVehicle = vehicles.value[object_id];
|
||
if (!existingVehicle) return;
|
||
|
||
// 检查是否有状态锁定
|
||
if (existingVehicle.statusLock &&
|
||
existingVehicle.statusLock.active &&
|
||
Date.now() < existingVehicle.statusLock.until) {
|
||
// 状态锁定中,不清除状态
|
||
return;
|
||
}
|
||
|
||
// 检查是否有超速状态 - 更严格的清除条件,避免图标来回切换
|
||
if (existingVehicle.speedViolation && existingVehicle.limitValue) {
|
||
// 如果当前速度低于限速值,清除超速状态
|
||
if (speed < existingVehicle.limitValue) {
|
||
// 添加更大的缓冲区,避免在临界值附近频繁切换状态
|
||
// 只有当速度低于限速值的70%时才清除超速状态,确保状态稳定
|
||
const speedBuffer = existingVehicle.limitValue * 0.7;
|
||
|
||
if (speed < speedBuffer) {
|
||
console.log(`检测到车辆${object_id}速度降低到${speed.toFixed(1)}km/h,低于限速${existingVehicle.limitValue}km/h的70%,考虑清除超速状态`);
|
||
|
||
// 检查是否应该清除超速状态
|
||
// 如果最后一次超速时间在15秒内,不清除状态以避免闪烁
|
||
const now = Date.now();
|
||
if (!existingVehicle.lastSpeedViolationTime || now - existingVehicle.lastSpeedViolationTime > 15000) {
|
||
// 检查状态锁定
|
||
if (!existingVehicle.statusLock ||
|
||
!existingVehicle.statusLock.active ||
|
||
now > existingVehicle.statusLock.until) {
|
||
// 清除超速状态
|
||
clearSpeedViolationStatus(object_id);
|
||
console.log(`已清除${object_id}的超速状态,恢复为普通状态`);
|
||
} else {
|
||
console.log(`车辆${object_id}状态锁定中,暂不清除超速状态`);
|
||
}
|
||
} else {
|
||
console.log(`车辆${object_id}刚刚处于超速状态,暂不清除状态以避免闪烁`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查是否有任何其他告警状态
|
||
const hasAlertStatus = existingVehicle.info || existingVehicle.warning ||
|
||
existingVehicle.alarm || existingVehicle.critical;
|
||
|
||
// 如果有告警状态,需要检查是否应该清除
|
||
if (hasAlertStatus) {
|
||
// 只处理超速状态(info)的自动恢复
|
||
// 1. 判断是否为超速状态
|
||
const isSpeedViolation = existingVehicle.info;
|
||
|
||
// 2. 判断当前速度是否已降低到安全值
|
||
// 这里使用一个默认阈值判断,实际应用中最好使用服务端返回的限速值
|
||
// 假设超速阈值为50km/h
|
||
const SPEED_THRESHOLD = 5; // 超速阈值,单位km/h
|
||
const isBelowThreshold = speed < SPEED_THRESHOLD;
|
||
|
||
// 3. 如果是超速状态且当前速度已降低到阈值以下,清除超速状态
|
||
if (isSpeedViolation && isBelowThreshold) {
|
||
console.log(`检测到车辆${object_id}速度降低到${speed.toFixed(1)}km/h,低于阈值${SPEED_THRESHOLD}km/h,自动清除超速状态`);
|
||
|
||
// 清除超速状态
|
||
existingVehicle.info = false;
|
||
|
||
// 更新车辆图标为默认图标
|
||
if (existingVehicle.feature) {
|
||
existingVehicle.feature.setStyle(styleManager.value.getVehicleStyle(object_id, speed, existingVehicle.heading));
|
||
}
|
||
|
||
// 更新标签显示为正常状态
|
||
if (existingVehicle.position) {
|
||
labelSystem.value.updateVehicleLabel(object_id, existingVehicle.position, speed);
|
||
}
|
||
|
||
console.log(`已清除${object_id}的超速状态,恢复为普通状态`);
|
||
} else {
|
||
console.log(`车辆${object_id}有告警状态,但不符合自动清除条件,保持该状态`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 发送心跳
|
||
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 (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
|
||
// 清理所有超速状态计时器
|
||
Object.keys(speedViolationTimers).forEach(vehicleId => {
|
||
clearTimeout(speedViolationTimers[vehicleId]);
|
||
delete speedViolationTimers[vehicleId];
|
||
});
|
||
|
||
// 移除图层
|
||
if (vehicleLayer && props.map) {
|
||
props.map.removeLayer(vehicleLayer);
|
||
vehicleLayer = null;
|
||
}
|
||
|
||
// 移除标签
|
||
if (props.map && labelSystem.value) {
|
||
Object.values(vehicles.value).forEach(vehicle => {
|
||
if (vehicle.overlay) {
|
||
console.log(`移除标签: ${vehicle.id}`);
|
||
labelSystem.value.removeVehicleLabel(vehicle.id);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 清空车辆数据
|
||
vehicles.value = {};
|
||
}
|
||
|
||
// 组件挂载时初始化
|
||
onMounted(() => {
|
||
// 创建图层并连接WebSocket
|
||
if (props.map) {
|
||
createVehicleLayer();
|
||
}
|
||
|
||
// 连接WebSocket
|
||
connectWebSocket();
|
||
|
||
// 设置定时发送心跳
|
||
const pingInterval = setInterval(() => {
|
||
if (wsService && wsConnected.value) {
|
||
sendPing();
|
||
}
|
||
}, 30000); // 每30秒发送一次心跳
|
||
|
||
// 组件卸载时清理资源
|
||
onUnmounted(() => {
|
||
clearInterval(pingInterval);
|
||
if (reconnectTimer) {
|
||
clearTimeout(reconnectTimer);
|
||
reconnectTimer = null;
|
||
}
|
||
cleanup();
|
||
});
|
||
});
|
||
|
||
// 组件被激活时(切换回该路由时)
|
||
onActivated(() => {
|
||
console.log('VehicleMovementControl组件被激活');
|
||
// 如果WebSocket未连接,重新连接
|
||
if (!wsConnected.value && !wsService) {
|
||
console.log('组件激活,WebSocket未连接,正在重新连接...');
|
||
// 延迟一点时间再连接,确保DOM已完全渲染
|
||
reconnectTimer = setTimeout(() => {
|
||
connectWebSocket();
|
||
}, 500);
|
||
} else {
|
||
console.log('组件激活,WebSocket已连接');
|
||
}
|
||
});
|
||
|
||
// 组件被停用时(切换到其他路由时)
|
||
onDeactivated(() => {
|
||
console.log('VehicleMovementControl组件被停用');
|
||
// 可以选择在这里关闭WebSocket,也可以保持连接
|
||
// 如果希望在组件隐藏时断开连接,可以取消下面的注释
|
||
/*
|
||
if (wsService) {
|
||
console.log('组件停用,关闭WebSocket连接');
|
||
wsService.close();
|
||
wsService = null;
|
||
wsConnected.value = false;
|
||
}
|
||
*/
|
||
});
|
||
|
||
// 监听地图实例变化
|
||
watch(() => props.map, (newMap) => {
|
||
if (newMap) {
|
||
createVehicleLayer();
|
||
}
|
||
});
|
||
|
||
// 向外暴露方法
|
||
defineExpose({
|
||
updateVehiclePosition,
|
||
wsConnected,
|
||
sendPing,
|
||
sendSubscribe,
|
||
vehicleCategories,
|
||
weatherStationVisible,
|
||
vehicleDetailVisible,
|
||
vehicleDetail,
|
||
toggleWeatherStationVisibility() {
|
||
weatherStationVisible.value = !weatherStationVisible.value;
|
||
},
|
||
toggleVehicleDetailVisibility() {
|
||
vehicleDetailVisible.value = !vehicleDetailVisible.value;
|
||
},
|
||
updateVehicleDetail(data) {
|
||
if (data) {
|
||
vehicleDetail.value = {...vehicleDetail.value, ...data};
|
||
vehicleDetailVisible.value = true;
|
||
}
|
||
},
|
||
showVehicleDetail(vehicleId, pixelPosition) {
|
||
if (vehicles.value[vehicleId]) {
|
||
// 更新车辆详情
|
||
vehicleDetail.value = {
|
||
id: vehicleId,
|
||
type: vehicles.value[vehicleId].type,
|
||
status: '任务中',
|
||
startTime: '11-19 11:30',
|
||
currentLocation: '当前位置',
|
||
startLocation: 'T1航站楼',
|
||
endLocation: 'T3航站楼',
|
||
totalDistance: '1.3km',
|
||
battery: '60%',
|
||
manager: '张三',
|
||
phone: '18661910988'
|
||
};
|
||
|
||
// 设置弹窗位置
|
||
if (pixelPosition) {
|
||
const viewportPosition = props.map.getViewport().getBoundingClientRect();
|
||
detailPopupStyle.value = {
|
||
left: (viewportPosition.left + pixelPosition[0] + 10) + 'px',
|
||
top: (viewportPosition.top + pixelPosition[1] + 10) + 'px'
|
||
};
|
||
}
|
||
|
||
// 显示详情弹窗
|
||
vehicleDetailVisible.value = true;
|
||
}
|
||
},
|
||
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 = 'AIRPORT_VEHICLE';
|
||
else if (vehicle.isShuttleVehicle) vehicleTypeKey = 'SHUTTLE_VEHICLE';
|
||
|
||
// 如果车辆属于当前修改的类别
|
||
if (vehicleTypeKey === type) {
|
||
// 更新图标可见性 - 如果不可见,则完全移除图标样式
|
||
if (vehicle.feature) {
|
||
if (visible) {
|
||
// 显示图标 - 确保styleManager.value存在
|
||
if (styleManager.value) {
|
||
vehicle.feature.setStyle(styleManager.value.getVehicleStyle(vehicle.id, vehicle.speed, vehicle.heading));
|
||
} else {
|
||
// 提供一个默认样式,根据车辆类型选择图标
|
||
vehicle.feature.setStyle(defaultGetVehicleStyle(vehicle.id, vehicle.speed, vehicle.heading));
|
||
}
|
||
} else {
|
||
// 完全隐藏图标,使用空样式而非null
|
||
vehicle.feature.setStyle(new Style({}));
|
||
}
|
||
}
|
||
|
||
// 单独控制文本标签可见性,与图标显示状态无关
|
||
if (labelSystem.value) {
|
||
labelSystem.value.setLabelVisibility(vehicle.id, showLabel);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
},
|
||
reconnectWebSocket() {
|
||
console.log('手动重连WebSocket');
|
||
connectWebSocket();
|
||
},
|
||
|
||
// 添加平滑动画相关方法
|
||
startVehicleSmoothing() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.startAnimationLoop();
|
||
}
|
||
},
|
||
|
||
stopVehicleSmoothing() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.stopAnimationLoop();
|
||
}
|
||
},
|
||
|
||
resetVehicleAnimations() {
|
||
if (animationSystem.value) {
|
||
animationSystem.value.resetAnimations();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 添加一个默认的getVehicleStyle函数
|
||
function defaultGetVehicleStyle(id, _speed, heading) {
|
||
// 检查车辆类型
|
||
const vehicle = vehicles.value[id];
|
||
if (!vehicle) return createDefaultStyle(carIcon, heading);
|
||
|
||
// 根据车辆类型选择图标
|
||
if (vehicle.isAircraftIn) {
|
||
// 滑入航空器(CA开头)使用黄色Aircraft1.png图标
|
||
return createDefaultStyle(aircraftInIcon, heading);
|
||
} else if (vehicle.isAircraftOut) {
|
||
// 滑出航空器(MU开头)使用蓝色Aircraft.png图标
|
||
return createDefaultStyle(aircraftOutIcon, heading);
|
||
} else if (vehicle.isUnmannedVehicle) {
|
||
// 无人车使用noPeopleCar.png图标
|
||
return createDefaultStyle(carIcon, heading);
|
||
} else if (vehicle.isSpecialVehicle) {
|
||
// 特勤车
|
||
return createDefaultStyle(carIcon, heading);
|
||
} else if (vehicle.isShuttleVehicle) {
|
||
// 摆渡车
|
||
return createDefaultStyle(carIcon, heading);
|
||
} else {
|
||
// 默认车辆图标
|
||
return createDefaultStyle(carIcon, heading);
|
||
}
|
||
}
|
||
|
||
// 创建默认样式的辅助函数
|
||
function createDefaultStyle(iconSrc, heading) {
|
||
return new Style({
|
||
image: new Icon({
|
||
src: iconSrc,
|
||
scale: 1.5,
|
||
anchor: [0.5, 0.5],
|
||
rotation: ((heading - 72) * Math.PI) / 180,
|
||
})
|
||
});
|
||
}
|
||
</script> |