211 lines
6.3 KiB
TypeScript
211 lines
6.3 KiB
TypeScript
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;
|
||
}
|
||
}
|