From 418c66c7218b95d7515410dab87279d5673a17bb Mon Sep 17 00:00:00 2001 From: xudan Date: Fri, 18 Jul 2025 15:52:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=BA=93=E4=BD=8D?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=9B=91=E6=8E=A7=E6=8E=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=93=E4=BD=8D=E4=BF=A1=E6=81=AF=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=AE=9A=E4=B9=89=EF=BC=8C=E5=A2=9E=E5=BC=BA=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E6=98=BE=E7=A4=BA=E5=BA=93=E4=BD=8D=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/scene/api.ts | 40 ++++- src/apis/scene/type.ts | 82 +++++++++ src/components/card/point-detail-card.vue | 120 +++++++++++++ src/pages/movement-supervision.vue | 75 ++++++-- src/services/editor.service.ts | 12 ++ src/services/storage-location.service.ts | 203 ++++++++++++++++++++++ 6 files changed, 517 insertions(+), 15 deletions(-) create mode 100644 src/services/storage-location.service.ts diff --git a/src/apis/scene/api.ts b/src/apis/scene/api.ts index 2fa7c17..0b96dae 100644 --- a/src/apis/scene/api.ts +++ b/src/apis/scene/api.ts @@ -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 { @@ -122,3 +122,41 @@ export async function monitorRealSceneById(id: SceneInfo['id']): Promise { + 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; + } +} diff --git a/src/apis/scene/type.ts b/src/apis/scene/type.ts index c695341..5b9fd4c 100644 --- a/src/apis/scene/type.ts +++ b/src/apis/scene/type.ts @@ -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; +} diff --git a/src/components/card/point-detail-card.vue b/src/components/card/point-detail-card.vue index d5e4b38..b83c261 100644 --- a/src/components/card/point-detail-card.vue +++ b/src/components/card/point-detail-card.vue @@ -1,5 +1,6 @@ + + diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index 5c65a81..1d25317 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -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(); +//#region 响应式状态定义 // 获取路由信息以判断当前模式 const route = useRoute(); const isMonitorMode = computed(() => route.path.includes('/monitor')); -//#region 接口 +// 场景标题 +const title = ref(''); + +// 服务实例 +const container = shallowRef(); +const editor = shallowRef(); +const storageLocationService = shallowRef(); +const client = shallowRef(); + +// 依赖注入 +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(''); - -const container = shallowRef(); -const editor = shallowRef(); -provide(EDITOR_KEY, editor); +//#region 服务初始化 onMounted(() => { editor.value = new EditorService(container.value!); + storageLocationService.value = new StorageLocationService(editor.value, props.sid); }); +//#endregion -const client = shallowRef(); +//#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(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(true); +//#endregion