web-map/src/services/storage-location.service.ts

211 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { StorageLocationInfo, StorageLocationMessage } from '@api/scene';
import { monitorStorageLocationById } from '@api/scene';
import type { EditorService } from '@core/editor.service';
import { isNil } from 'lodash-es';
import { type Ref, ref } from 'vue';
/**
* 库位处理服务类
* 负责管理库位数据、WebSocket连接、状态更新和画布点颜色管理
*/
export class StorageLocationService {
private storageClient: Ref<WebSocket | undefined> = ref();
private storageLocations: Ref<Map<string, StorageLocationInfo[]>> = ref(new Map());
private editor: EditorService | null = null;
private sceneId: string = '';
constructor(editor: EditorService, sceneId: string) {
this.editor = editor;
this.sceneId = sceneId;
}
/**
* 获取库位数据
*/
get locations() {
return this.storageLocations;
}
/**
* 建立站点名称到画布点ID的映射关系
* @returns 站点名称到画布点ID的映射Map
*/
private buildStationToPointIdMap(): Map<string, string> {
const stationToPointIdMap = new Map<string, string>();
if (!this.editor) return stationToPointIdMap;
// 获取所有动作点 (MapPointType.动作点 = 15)
const actionPoints = this.editor.find('point').filter((pen) => pen.point?.type === 15);
actionPoints.forEach((pen) => {
const stationName = pen.label; // 如 "AP9"
const pointId = pen.id; // 如 "3351"
if (stationName && pointId) {
stationToPointIdMap.set(stationName, pointId);
}
});
return stationToPointIdMap;
}
/**
* 根据库位ID获取对应的画布点ID
* @param locationId 库位ID
* @returns 画布点ID或null
*/
private getPointIdByStorageLocationId(locationId: string): string | null {
for (const [pointId, locations] of this.storageLocations.value.entries()) {
if (locations.some((loc) => loc.id === locationId)) {
return pointId;
}
}
return null;
}
/**
* 更新Map中的库位信息
* @param pointId 画布点ID
* @param locationId 库位ID
* @param newStatus 新状态
*/
private updateStorageLocationInMap(pointId: string, locationId: string, newStatus: Partial<StorageLocationInfo>) {
const locations = this.storageLocations.value.get(pointId);
if (locations) {
const index = locations.findIndex((loc) => loc.id === locationId);
if (index !== -1) {
locations[index] = { ...locations[index], ...newStatus };
}
}
}
/**
* 更新单个动作点的边框颜色
* @param pointId 画布点ID
*/
private updatePointBorderColor(pointId: string) {
const locations = this.storageLocations.value.get(pointId);
if (!locations || locations.length === 0) {
// 没有绑定库位,保持默认灰色
return;
}
const allOccupied = locations.every((loc) => loc.is_occupied);
const allLocked = locations.every((loc) => loc.is_locked);
const color = allOccupied ? '#ff4d4f' : '#52c41a'; // 占用红色,否则绿色
// 更新边框颜色
this.editor?.updatePointBorderColor(pointId, color);
// 更新锁定图标状态
this.editor?.updatePointLockIcon(pointId, allLocked);
}
/**
* 更新所有动作点的边框颜色
*/
private updatePointBorderColors() {
for (const [pointId] of this.storageLocations.value.entries()) {
this.updatePointBorderColor(pointId);
}
}
/**
* 处理库位状态更新消息
* @param message 库位状态更新消息
*/
private handleStorageLocationUpdate(message: StorageLocationMessage) {
if (message.type === 'storage_location_update') {
// 优先使用后端提供的 operate_point_id 直接映射到画布点ID
// 若无该字段或为空,再回退通过站点名映射到点标签。
const stationToPointIdMap = this.buildStationToPointIdMap();
// 按画布点ID组织库位数据
const locationsByPointId = new Map<string, StorageLocationInfo[]>();
message.data.storage_locations.forEach((location) => {
const byOperateId = location.operate_point_id; // 直接对应动作点ID
const byStationName = stationToPointIdMap.get(location.station_name);
const pointId = byOperateId || byStationName;
if (pointId) {
if (!locationsByPointId.has(pointId)) {
locationsByPointId.set(pointId, []);
}
locationsByPointId.get(pointId)!.push(location);
}
});
this.storageLocations.value = locationsByPointId;
// 更新动作点的边框颜色
this.updatePointBorderColors();
} else if (message.type === 'storage_location_status_change') {
// 处理单个库位状态变化
const { new_status } = message;
const pointId = this.getPointIdByStorageLocationId(new_status.id);
if (pointId) {
this.updateStorageLocationInMap(pointId, new_status.id, new_status);
this.updatePointBorderColor(pointId);
}
}
}
/**
* 启动库位监控
* @param options 监控选项
*/
async startMonitoring(options: { interval?: number } = {}) {
this.stopMonitoring();
// 监控库位状态
const ws = await monitorStorageLocationById(this.sceneId, { interval: options.interval || 3 });
if (isNil(ws)) return;
ws.onmessage = (e) => {
try {
const message = <StorageLocationMessage>JSON.parse(e.data || '{}');
this.handleStorageLocationUpdate(message);
} catch (error) {
console.debug('处理库位状态消息失败:', error);
}
};
// 连接成功后主动请求当前状态
ws.onopen = () => {
const message = {
type: 'get_status',
timestamp: new Date().toISOString(),
};
ws.send(JSON.stringify(message));
};
this.storageClient.value = ws;
}
/**
* 停止库位监控
*/
stopMonitoring() {
this.storageClient.value?.close();
this.storageClient.value = undefined;
}
/**
* 获取指定画布点的库位信息
* @param pointId 画布点ID
* @returns 库位信息数组
*/
getLocationsByPointId(pointId: string): StorageLocationInfo[] | undefined {
return this.storageLocations.value.get(pointId);
}
/**
* 清理资源
*/
destroy() {
this.stopMonitoring();
this.storageLocations.value.clear();
this.editor = null;
}
}