feat: 添加库位状态监控功能,更新相关接口、主题和组件以支持状态显示

This commit is contained in:
xudan 2025-07-14 15:55:46 +08:00
parent cfb04396e2
commit 4379bcc533
7 changed files with 112 additions and 4 deletions

View File

@ -16,6 +16,9 @@ export interface MapPen extends Pen {
activeAttrs?: Array<string>; // 已激活的额外属性
properties?: unknown; // 第三方附加参数
storageStatus?: Record<string, unknown>; // 库位状态
statusStyle?: string; // 状态颜色
strokeStyle?: string; // 边框颜色
}
//#region 点位

View File

@ -14,6 +14,7 @@ const enum API {
= '/scene/monitor/:id',
= '/scene/monitor/real/:id',
= '/scene/monitor/storage/:id',
}
export async function getSceneById(id: SceneInfo['id']): Promise<SceneDetail | null> {
@ -89,6 +90,17 @@ export async function saveSceneByGroupId(id: RobotGroup['id'], sid: RobotGroup['
}
}
export async function monitorStorageStatusById(id: SceneInfo['id']): Promise<WebSocket | null> {
if (!id) return null;
try {
const socket = await ws.create(API..replace(':id', id));
return socket;
} catch (error) {
console.debug(error);
return null;
}
}
export async function monitorSceneById(id: SceneInfo['id']): Promise<WebSocket | null> {
if (!id) return null;
try {

View File

@ -12,7 +12,14 @@
},
"point-l": {
"stroke": "#595959",
"strokeActive": "#FCC947"
"strokeActive": "#FCC947",
"stroke-occupied": "#ff4d4f",
"stroke-unoccupied": "#52c41a",
"stroke-empty": "#1890ff",
"stroke-disabled": "#bfbfbf",
"stroke-enabled": "#52c41a",
"stroke-locked": "#faad14",
"stroke-unlocked": "#52c41a"
},
"route": {
"strokeActive": "#FCC947",

View File

@ -12,7 +12,14 @@
},
"point-l": {
"stroke": "#595959",
"strokeActive": "#EBB214"
"strokeActive": "#EBB214",
"stroke-occupied": "#ff4d4f",
"stroke-unoccupied": "#52c41a",
"stroke-empty": "#1890ff",
"stroke-disabled": "#bfbfbf",
"stroke-enabled": "#52c41a",
"stroke-locked": "#faad14",
"stroke-unlocked": "#52c41a"
},
"route": {
"strokeActive": "#EBB214",

View File

@ -52,6 +52,13 @@ const mapAreas = (type: MapAreaType): string => {
};
const coArea1 = computed<string>(() => mapAreas(MapAreaType.库区));
const coArea2 = computed<string>(() => mapAreas(MapAreaType.互斥区));
const storageStatus = computed<string>(() => {
const status = pen.value?.storageStatus;
if (!status) return '暂无';
return Object.entries(status)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
});
</script>
<template>
@ -113,6 +120,12 @@ const coArea2 = computed<string>(() => mapAreas(MapAreaType.互斥区));
<a-typography-text>{{ coArea2 || $t('暂无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="point.type === MapPointType.动作点">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('库位状态') }}</a-typography-text>
<a-typography-text>{{ storageStatus }}</a-typography-text>
</a-flex>
</a-list-item>
</a-list>
</template>
<a-empty v-else :image="sTheme.empty" />

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import { MapPointType } from '@api/map';
import type { RobotRealtimeInfo } from '@api/robot';
import { getSceneByGroupId, getSceneById, monitorRealSceneById, monitorSceneById } from '@api/scene';
import { EditorService } from '@core/editor.service';
@ -46,6 +47,53 @@ const monitorScene = async () => {
};
//#endregion
//#region :
const storageStatusClient = shallowRef<WebSocket>();
const monitorStorageStatus = async () => {
storageStatusClient.value?.close();
// const ws = await monitorStorageStatusById(props.sid);
// if (isNil(ws)) return;
// --- WebSocket Simulation Start ---
const messageHandler = (data: string) => {
const { locationName, statusValue, statusAttributes } = JSON.parse(data || '{}');
if (!locationName) return;
const pen = editor.value?.find(locationName)[0];
if (pen?.id) {
editor.value?.refreshPoint(pen.id, { status: statusValue, attributes: statusAttributes });
}
};
const simulationTimer = setInterval(() => {
if (!editor.value) return;
//
const actionPoints = editor.value.find('point').filter((p) => p.point?.type === MapPointType.动作点);
if (!actionPoints.length) return;
const randomPoint = actionPoints[Math.floor(Math.random() * actionPoints.length)];
const statuses = ['Occupied', 'Unoccupied', 'Empty', 'Disabled', 'Enabled', 'Locked', 'Unlocked'];
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
const mockData = {
locationName: randomPoint.id,
statusValue: randomStatus,
statusAttributes: {
Status: randomStatus,
Timestamp: new Date().toISOString(),
},
};
messageHandler(JSON.stringify(mockData));
}, 2000);
storageStatusClient.value = {
close: () => {
clearInterval(simulationTimer);
},
} as WebSocket;
// --- WebSocket Simulation End ---
};
//#endregion
const title = ref<string>('');
const container = shallowRef<HTMLDivElement>();
@ -60,11 +108,13 @@ onMounted(async () => {
await readScene();
await editor.value?.initRobots();
await monitorScene();
await monitorStorageStatus();
//
await handleAutoSaveAndRestoreViewState();
});
onUnmounted(() => {
client.value?.close();
storageStatusClient.value?.close();
});
const show = ref<boolean>(true);

View File

@ -696,6 +696,22 @@ export class EditorService extends Meta2d {
const image = type < 10 ? '' : `${import.meta.env.BASE_URL}/point/${type}-${theme}.png`;
return { image, canvasLayer: CanvasLayer.CanvasMain };
}
public refreshPoint(id: string, info: { status: string; attributes: Record<string, unknown> }): void {
const pen = this.getPenById(id);
if (!pen?.point) return;
const color = get(
sTheme.editor,
`point-l.stroke-${info.status.toLowerCase()}`,
get(sTheme.editor, 'point-l.stroke'),
);
this.setValue(
{ id, statusStyle: color, storageStatus: info.attributes },
{ render: true, history: false, doEvent: false },
);
}
//#endregion
//#region 线路
@ -948,7 +964,7 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
const { type } = pen.point ?? {};
const { label = '' } = pen ?? {};
const { label = '', statusStyle } = pen ?? {};
ctx.save();
switch (type) {
@ -996,7 +1012,7 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
case MapPointType.:
case MapPointType.:
ctx.roundRect(x, y, w, h, r);
ctx.strokeStyle = get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke') ?? '';
ctx.strokeStyle = statusStyle ?? get(theme, active ? 'point-l.strokeActive' : 'point-l.stroke') ?? '';
ctx.stroke();
break;
default: