feat: 更新库位状态监控接口,添加库位信息类型定义,增强组件以显示库位状态标签
This commit is contained in:
parent
107ef11106
commit
418c66c721
@ -14,7 +14,7 @@ const enum API {
|
||||
|
||||
实时监控场景 = '/scene/monitor/:id',
|
||||
实时监控真实场景 = '/scene/monitor/real/:id',
|
||||
实时监控库位状态 = '/scene/monitor/storage/:id',
|
||||
实时监控库位状态 = '/ws/storage-location/:id',
|
||||
}
|
||||
|
||||
export async function getSceneById(id: SceneInfo['id']): Promise<SceneDetail | null> {
|
||||
@ -122,3 +122,41 @@ export async function monitorRealSceneById(id: SceneInfo['id']): Promise<WebSock
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function monitorStorageLocationById(
|
||||
id: SceneInfo['id'],
|
||||
options?: {
|
||||
interval?: number;
|
||||
storage_area_id?: string;
|
||||
station_name?: string;
|
||||
layer_name?: string;
|
||||
is_occupied?: boolean;
|
||||
is_locked?: boolean;
|
||||
is_disabled?: boolean;
|
||||
},
|
||||
): Promise<WebSocket | null> {
|
||||
if (!id) return null;
|
||||
try {
|
||||
let url = API.实时监控库位状态.replace(':id', id);
|
||||
|
||||
// 构建查询参数
|
||||
const params = new URLSearchParams();
|
||||
if (options?.interval !== undefined) params.append('interval', options.interval.toString());
|
||||
if (options?.storage_area_id) params.append('storage_area_id', options.storage_area_id);
|
||||
if (options?.station_name) params.append('station_name', options.station_name);
|
||||
if (options?.layer_name) params.append('layer_name', options.layer_name);
|
||||
if (options?.is_occupied !== undefined) params.append('is_occupied', options.is_occupied.toString());
|
||||
if (options?.is_locked !== undefined) params.append('is_locked', options.is_locked.toString());
|
||||
if (options?.is_disabled !== undefined) params.append('is_disabled', options.is_disabled.toString());
|
||||
|
||||
if (params.toString()) {
|
||||
url += '?' + params.toString();
|
||||
}
|
||||
|
||||
const socket = await ws.create(import.meta.env.ENV_STORAGE_WEBSOCKET_BASE + url);
|
||||
return socket;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -74,3 +74,85 @@ export interface StandardSceneArea {
|
||||
config?: object; // 其它属性配置(可按需增加)
|
||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||
}
|
||||
|
||||
// 库位状态相关类型定义
|
||||
export interface StorageLocationInfo {
|
||||
id: string; // 层ID
|
||||
layer_index: number; // 层索引(从1开始)
|
||||
layer_name: string; // 层名称、库位名称
|
||||
operate_point_id: string; // 动作点ID
|
||||
station_name: string; // 站点名称
|
||||
scene_id: string; // 场景ID
|
||||
storage_area_id: string; // 库区ID
|
||||
area_name: string; // 库区名称
|
||||
is_occupied: boolean; // 是否占用
|
||||
is_locked: boolean; // 是否锁定
|
||||
is_disabled: boolean; // 是否禁用
|
||||
is_empty_tray: boolean; // 是否空托盘
|
||||
locked_by: string | null; // 锁定者
|
||||
goods_content: string; // 货物内容
|
||||
goods_weight: number | null; // 货物重量(克)
|
||||
goods_volume: number | null; // 货物体积(立方厘米)
|
||||
goods_stored_at: string | null; // 货物存放时间
|
||||
goods_retrieved_at: string | null; // 货物取出时间
|
||||
last_access_at: string; // 最后访问时间
|
||||
max_weight: number; // 最大承重(克)
|
||||
max_volume: number; // 最大体积(立方厘米)
|
||||
layer_height: number; // 层高(毫米)
|
||||
tags: string; // 标签
|
||||
description: string | null; // 层描述
|
||||
created_at: string; // 创建时间
|
||||
updated_at: string; // 更新时间
|
||||
}
|
||||
|
||||
// WebSocket消息类型
|
||||
export interface StorageLocationUpdateMessage {
|
||||
type: 'storage_location_update';
|
||||
scene_id: string;
|
||||
timestamp: string;
|
||||
message: string;
|
||||
data: {
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
storage_locations: StorageLocationInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorageLocationStatusChangeMessage {
|
||||
type: 'storage_location_status_change';
|
||||
scene_id: string;
|
||||
layer_name: string;
|
||||
action: string;
|
||||
timestamp: string;
|
||||
new_status: {
|
||||
id: string;
|
||||
is_occupied: boolean;
|
||||
is_locked: boolean;
|
||||
is_disabled: boolean;
|
||||
is_empty_tray: boolean;
|
||||
locked_by: string | null;
|
||||
goods_content: string;
|
||||
last_access_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StorageLocationErrorMessage {
|
||||
type: 'error';
|
||||
scene_id: string;
|
||||
timestamp: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type StorageLocationMessage =
|
||||
| StorageLocationUpdateMessage
|
||||
| StorageLocationStatusChangeMessage
|
||||
| StorageLocationErrorMessage;
|
||||
|
||||
// 客户端发送消息类型
|
||||
export interface StorageLocationClientMessage {
|
||||
type: 'get_status';
|
||||
timestamp: string;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { MapAreaType, type MapPen, type MapPointInfo, MapPointType, type Rect } from '@api/map';
|
||||
import type { StorageLocationInfo } from '@api/scene';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { isNil } from 'lodash-es';
|
||||
@ -8,6 +9,7 @@ import { computed, inject, type InjectionKey, type ShallowRef } from 'vue';
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
current?: string;
|
||||
storageLocations?: StorageLocationInfo[]; // 当前动作点绑定的库位信息
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
const editor = inject(props.token)!;
|
||||
@ -52,6 +54,31 @@ const mapAreas = (type: MapAreaType): string => {
|
||||
};
|
||||
const coArea1 = computed<string>(() => mapAreas(MapAreaType.库区));
|
||||
const coArea2 = computed<string>(() => mapAreas(MapAreaType.互斥区));
|
||||
|
||||
// 库位状态标签样式映射
|
||||
const getStorageStatusTag = (location: StorageLocationInfo) => {
|
||||
const tags = [];
|
||||
|
||||
if (location.is_occupied) {
|
||||
tags.push({ text: '已占用', color: 'error' });
|
||||
} else {
|
||||
tags.push({ text: '空闲', color: 'success' });
|
||||
}
|
||||
|
||||
if (location.is_locked) {
|
||||
tags.push({ text: '已锁定', color: 'warning' });
|
||||
}
|
||||
|
||||
if (location.is_disabled) {
|
||||
tags.push({ text: '已禁用', color: 'error' });
|
||||
}
|
||||
|
||||
if (location.is_empty_tray) {
|
||||
tags.push({ text: '空托盘', color: 'processing' });
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -123,8 +150,101 @@ const coArea2 = computed<string>(() => mapAreas(MapAreaType.互斥区));
|
||||
<a-typography-text>{{ coArea2 || $t('暂无') }}</a-typography-text>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="MapPointType.动作点 === point.type && storageLocations?.length">
|
||||
<a-flex :gap="8" vertical>
|
||||
<a-typography-text type="secondary">{{ $t('库位状态') }}</a-typography-text>
|
||||
<div class="storage-locations">
|
||||
<div v-for="location in storageLocations" :key="location.id" class="storage-item">
|
||||
<a-typography-text class="storage-name">{{ location.layer_name }}</a-typography-text>
|
||||
<div class="storage-tags">
|
||||
<div
|
||||
v-for="tag in getStorageStatusTag(location)"
|
||||
:key="tag.text"
|
||||
:class="['storage-tag', `storage-tag-${tag.color}`]"
|
||||
>
|
||||
{{ tag.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-flex>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '/src/assets/themes/theme' as *;
|
||||
|
||||
@include themed {
|
||||
.storage-locations {
|
||||
.storage-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid get-color(border1);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.storage-name {
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
color: get-color(text1);
|
||||
}
|
||||
|
||||
.storage-tags {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.storage-tag {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid;
|
||||
|
||||
&-error {
|
||||
color: get-color(error_text);
|
||||
background-color: get-color(error_bg);
|
||||
border-color: get-color(error_border);
|
||||
}
|
||||
|
||||
&-success {
|
||||
color: get-color(success_text);
|
||||
background-color: get-color(success_bg);
|
||||
border-color: get-color(success_border);
|
||||
}
|
||||
|
||||
&-warning {
|
||||
color: get-color(warning_text);
|
||||
background-color: get-color(warning_bg);
|
||||
border-color: get-color(warning_border);
|
||||
}
|
||||
|
||||
&-processing {
|
||||
color: get-color(primary_text);
|
||||
background-color: get-color(primary_bg);
|
||||
border-color: get-color(primary_border);
|
||||
}
|
||||
|
||||
&-default {
|
||||
color: get-color(text2);
|
||||
background-color: get-color(fill2);
|
||||
border-color: get-color(border1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import type { RobotRealtimeInfo } from '@api/robot';
|
||||
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '@api/scene';
|
||||
import { EditorService } from '@core/editor.service';
|
||||
import { StorageLocationService } from '@core/storage-location.service';
|
||||
import { useViewState } from '@core/useViewState';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { isNil } from 'lodash-es';
|
||||
@ -16,18 +17,39 @@ type Props = {
|
||||
};
|
||||
const props = defineProps<Props>();
|
||||
|
||||
//#region 响应式状态定义
|
||||
// 获取路由信息以判断当前模式
|
||||
const route = useRoute();
|
||||
const isMonitorMode = computed(() => route.path.includes('/monitor'));
|
||||
|
||||
//#region 接口
|
||||
// 场景标题
|
||||
const title = ref<string>('');
|
||||
|
||||
// 服务实例
|
||||
const container = shallowRef<HTMLDivElement>();
|
||||
const editor = shallowRef<EditorService>();
|
||||
const storageLocationService = shallowRef<StorageLocationService>();
|
||||
const client = shallowRef<WebSocket>();
|
||||
|
||||
// 依赖注入
|
||||
provide(EDITOR_KEY, editor);
|
||||
//#endregion
|
||||
|
||||
//#region 核心服务接口
|
||||
/**
|
||||
* 读取场景数据
|
||||
*/
|
||||
const readScene = async () => {
|
||||
const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid);
|
||||
title.value = res?.label ?? '';
|
||||
editor.value?.load(res?.json);
|
||||
};
|
||||
|
||||
/**
|
||||
* 监控场景中的机器人状态
|
||||
*/
|
||||
const monitorScene = async () => {
|
||||
console.log(current.value?.id);
|
||||
client.value?.close();
|
||||
// 根据路由路径是否是真实场景监控决定使用哪个监控接口
|
||||
const ws = isMonitorMode.value ? await monitorRealSceneById(props.sid) : await monitorSceneById(props.sid);
|
||||
@ -46,29 +68,33 @@ const monitorScene = async () => {
|
||||
};
|
||||
//#endregion
|
||||
|
||||
const title = ref<string>('');
|
||||
|
||||
const container = shallowRef<HTMLDivElement>();
|
||||
const editor = shallowRef<EditorService>();
|
||||
provide(EDITOR_KEY, editor);
|
||||
//#region 服务初始化
|
||||
onMounted(() => {
|
||||
editor.value = new EditorService(container.value!);
|
||||
storageLocationService.value = new StorageLocationService(editor.value, props.sid);
|
||||
});
|
||||
//#endregion
|
||||
|
||||
const client = shallowRef<WebSocket>();
|
||||
//#region 生命周期管理
|
||||
onMounted(async () => {
|
||||
await readScene();
|
||||
await editor.value?.initRobots();
|
||||
await monitorScene();
|
||||
await storageLocationService.value?.startMonitoring({ interval: 3 });
|
||||
// 自动保存和恢复视图状态
|
||||
await handleAutoSaveAndRestoreViewState();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
client.value?.close();
|
||||
storageLocationService.value?.destroy();
|
||||
});
|
||||
//#endregion
|
||||
|
||||
const show = ref<boolean>(true);
|
||||
//#region 选择状态管理
|
||||
const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>();
|
||||
|
||||
// 监听编辑器选择状态变化
|
||||
watch(
|
||||
() => editor.value?.selected.value[0],
|
||||
(v) => {
|
||||
@ -81,22 +107,31 @@ watch(
|
||||
current.value = undefined;
|
||||
},
|
||||
);
|
||||
|
||||
// 计算当前选择的对象类型
|
||||
const isRobot = computed(() => current.value?.type === 'robot');
|
||||
const isPoint = computed(() => current.value?.type === 'point');
|
||||
const isRoute = computed(() => current.value?.type === 'line');
|
||||
const isArea = computed(() => current.value?.type === 'area');
|
||||
|
||||
/**
|
||||
* 选择机器人
|
||||
* @param id 机器人ID
|
||||
*/
|
||||
const selectRobot = (id: string) => {
|
||||
current.value = { type: 'robot', id };
|
||||
editor.value?.inactive();
|
||||
// 聚焦到机器人位置
|
||||
editor.value?.gotoById(id);
|
||||
};
|
||||
//#endregion
|
||||
|
||||
// 视图状态管理
|
||||
//#region 视图状态管理
|
||||
const { saveViewState, autoSaveAndRestoreViewState, isSaving } = useViewState();
|
||||
|
||||
// 保存当前视图状态
|
||||
/**
|
||||
* 保存当前视图状态
|
||||
*/
|
||||
const handleSaveViewState = async () => {
|
||||
if (!editor.value) return;
|
||||
|
||||
@ -108,12 +143,19 @@ const handleSaveViewState = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 自动保存和恢复视图状态
|
||||
/**
|
||||
* 自动保存和恢复视图状态
|
||||
*/
|
||||
const handleAutoSaveAndRestoreViewState = async () => {
|
||||
if (!editor.value) return;
|
||||
|
||||
await autoSaveAndRestoreViewState(editor.value, props.sid, props.id);
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//#region UI状态管理
|
||||
const show = ref<boolean>(true);
|
||||
//#endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -131,9 +173,9 @@ const handleAutoSaveAndRestoreViewState = async () => {
|
||||
<a-tab-pane key="1" :tab="$t('机器人')">
|
||||
<RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
|
||||
</a-tab-pane>
|
||||
<!-- <a-tab-pane key="2" :tab="$t('库区')">
|
||||
<a-tab-pane key="2" :tab="$t('库区')">
|
||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
|
||||
</a-tab-pane> -->
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" :tab="$t('高级组')">
|
||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
||||
</a-tab-pane>
|
||||
@ -151,7 +193,12 @@ const handleAutoSaveAndRestoreViewState = async () => {
|
||||
</a-float-button>
|
||||
<div v-if="show" class="card-container">
|
||||
<RobotDetailCard v-if="isRobot" :token="EDITOR_KEY" :current="current.id" />
|
||||
<PointDetailCard v-if="isPoint" :token="EDITOR_KEY" :current="current.id" />
|
||||
<PointDetailCard
|
||||
v-if="isPoint"
|
||||
:token="EDITOR_KEY"
|
||||
:current="current.id"
|
||||
:storage-locations="storageLocationService?.getLocationsByPointId(current.id)"
|
||||
/>
|
||||
<RouteDetailCard v-if="isRoute" :token="EDITOR_KEY" :current="current.id" />
|
||||
<AreaDetailCard v-if="isArea" :token="EDITOR_KEY" :current="current.id" />
|
||||
</div>
|
||||
|
@ -609,6 +609,18 @@ export class EditorService extends Meta2d {
|
||||
this.setValue({ ...pen, id }, { render: true, history: record, doEvent: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新动作点的边框颜色
|
||||
* @param pointId 动作点ID
|
||||
* @param color 边框颜色(如 '#ff4d4f' 红色或 '#52c41a' 绿色)
|
||||
*/
|
||||
public updatePointBorderColor(pointId: string, color: string): void {
|
||||
const pen = this.getPenById(pointId);
|
||||
if (!pen || pen.name !== 'point') return;
|
||||
|
||||
this.updatePen(pointId, { statusStyle: color }, false);
|
||||
}
|
||||
|
||||
//#region 实时机器人
|
||||
public async initRobots(): Promise<void> {
|
||||
await Promise.all(
|
||||
|
203
src/services/storage-location.service.ts
Normal file
203
src/services/storage-location.service.ts
Normal file
@ -0,0 +1,203 @@
|
||||
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 color = allOccupied ? '#ff4d4f' : '#52c41a'; // 全部占用红色,否则绿色
|
||||
|
||||
// 通知编辑器更新点的边框颜色
|
||||
this.editor?.updatePointBorderColor(pointId, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有动作点的边框颜色
|
||||
*/
|
||||
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') {
|
||||
// 建立站点名称到画布点ID的映射
|
||||
const stationToPointIdMap = this.buildStationToPointIdMap();
|
||||
|
||||
// 按画布点ID组织库位数据
|
||||
const locationsByPointId = new Map<string, StorageLocationInfo[]>();
|
||||
message.data.storage_locations.forEach((location) => {
|
||||
const stationName = location.station_name; // 如 "AP9"
|
||||
const pointId = stationToPointIdMap.get(stationName); // 获取对应的画布点ID,如 "3351"
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user