Compare commits

...

9 Commits

11 changed files with 120 additions and 1714 deletions

View File

@ -18,6 +18,8 @@ export enum MapPointType {
,
/** 库区点 - 仓储作业区域 */
,
/** 不可避让点 - 机器人不可避让的点位 */
= 7,
/** 密集库区点 - 密集库区点位 */
// 密集库区点,

View File

@ -32,6 +32,7 @@ export interface MapPointInfo {
deviceId?: string; // 设备ID
enabled?: 0 | 1; // 是否启用仅停靠点使用0=禁用1=启用)
deviceStatus?: number; // 设备状态仅自动门点使用0=关门1=开门)
isConnected?: boolean; // 连接状态仅自动门点使用true=已连接false=未连接)
active?: boolean; // 是否激活状态,用于控制光圈显示
}
//#endregion

View File

@ -8,7 +8,8 @@
"fill-2": "#69C6F5",
"fill-3": "#E48B1D",
"fill-4": "#E48B1D",
"fill-5": "#a72b69"
"fill-5": "#a72b69",
"fill-6": "#E63A3A"
},
"point-l": {
"stroke": "#595959",

View File

@ -8,7 +8,8 @@
"fill-2": "#69C6F5",
"fill-3": "#E48B1D",
"fill-4": "#E48B1D",
"fill-5": "#a72b69"
"fill-5": "#a72b69",
"fill-6": "#E63A3A"
},
"point-l": {
"stroke": "#595959",

View File

@ -198,6 +198,19 @@ const binTaskData = computed(() => {
<a-typography-text type="secondary">{{ $t('设备ID') }}</a-typography-text>
<a-typography-text>{{ point.deviceId }}</a-typography-text>
</a-list-item>
<a-list-item v-if="point.type === MapPointType.自动门点">
<a-typography-text type="secondary">{{ $t('连接状态') }}</a-typography-text>
<a-flex align="center" :gap="8" class="conn-status">
<span
class="status-dot"
:class="point.isConnected ? 'online' : 'offline'"
:title="point.isConnected ? $t('已连接') : $t('未连接')"
/>
<a-typography-text>
{{ point.isConnected ? $t('已连接') : $t('未连接') }}
</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="point.extensionType">
<a-typography-text type="secondary">{{ $t('扩展类型') }}</a-typography-text>
<a-typography-text>{{ $t(MapPointType[point.extensionType]) }}</a-typography-text>
@ -315,6 +328,24 @@ const binTaskData = computed(() => {
@use '/src/assets/themes/theme' as *;
@include themed {
.conn-status {
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
&.online {
background-color: get-color(success);
box-shadow: 0 0 0 2px rgba(82, 196, 26, 0.15);
}
&.offline {
background-color: get-color(error);
box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.15);
}
}
}
.storage-locations {
.storage-item {
display: flex;

View File

@ -223,7 +223,7 @@ const toRemoveRobots = () =>
/>
<span>{{ label }}</span>
</a-space>
<a-button
<!-- <a-button
v-if="showGroupEdit && !editable"
class="open-group-btn icon-btn"
size="small"
@ -231,7 +231,7 @@ const toRemoveRobots = () =>
@click.stop="toEditGroup(id)"
>
<i class="icon edit_group" />
</a-button>
</a-button> -->
</a-flex>
</template>

View File

@ -92,7 +92,7 @@ const monitorScene = async () => {
// refreshRobot
let processedPath: Array<{ x: number; y: number }> | undefined;
if (points?.length) {
if (points?.length && !isMonitorMode.value) {
//
const cx = x || 37; // X37
const cy = y || 37; // Y37
@ -114,7 +114,8 @@ const monitorScene = async () => {
} else {
const newX = x - 60;
const newY = y - 60;
const rotate = angle;
// angle
const rotate = angle == null ? undefined : -angle + 180;
return { id, x: newX, y: newY, rotate, visible: true };
}
});
@ -312,6 +313,11 @@ const handleAutoSaveAndRestoreViewState = async () => {
//#region UI
const show = ref<boolean>(true);
//#endregion
// iframe
const backToCards = () => {
window.parent?.postMessage({ type: 'scene_return_to_cards' }, '*');
};
</script>
<template>
@ -319,7 +325,10 @@ const show = ref<boolean>(true);
<a-layout-header class="p-16" style="height: 64px">
<a-flex justify="space-between" align="center">
<a-typography-text class="title">{{ title }}--{{ isMonitorMode ? '场景监控' : '场景仿真' }}</a-typography-text>
<a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button>
<a-space align="center">
<!-- <a-button @click="backToCards"> 返回 </a-button> -->
<a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button>
</a-space>
</a-flex>
</a-layout-header>

View File

@ -157,6 +157,10 @@ const handleAutoSaveAndRestoreViewState = async () => {
await autoSaveAndRestoreViewState(editor.value, props.id);
};
// iframe
const backToCards = () => {
window.parent?.postMessage({ type: 'scene_return_to_cards' }, '*');
};
</script>
<template>
@ -165,6 +169,7 @@ const handleAutoSaveAndRestoreViewState = async () => {
<a-flex justify="space-between" align="center">
<a-typography-text class="title">{{ title }} --场景编辑</a-typography-text>
<a-space align="center">
<!-- <a-button @click="backToCards"> 返回 </a-button> -->
<a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button>
<a-button v-if="editable" class="warning" @click="editable = false">
<i class="icon exit size-18 mr-8" />

View File

@ -88,10 +88,13 @@ export class AutoDoorService {
private timers = new Map<string, NodeJS.Timeout>();
private statusMap = new Map<string, 0 | 1>();
private editorService: EditorService | null = null;
private latestAutoDoorData = new Map<string, { deviceId: string; deviceStatus: number; active: boolean }>();
private latestAutoDoorData = new Map<
string,
{ deviceId: string; deviceStatus: number; active: boolean; isConnected: boolean }
>();
// 设备ID到自动门点ID的映射缓存避免每次都遍历查找
private deviceIdToPointIdMap = new Map<string, string>();
// 设备ID到自动门点ID列表的映射缓存,支持同一设备绑定多个点位
private deviceIdToPointIdMap = new Map<string, string[]>();
/**
*
@ -124,13 +127,14 @@ export class AutoDoorService {
if (pen.name === 'point' && (pen as MapPen).point?.type === MapPointType.) {
const deviceId = (pen as MapPen).point?.deviceId;
if (deviceId && pen.id) {
this.deviceIdToPointIdMap.set(deviceId, pen.id);
const list = this.deviceIdToPointIdMap.get(deviceId) ?? [];
list.push(pen.id);
this.deviceIdToPointIdMap.set(deviceId, list);
autoDoorCount++;
}
}
});
console.log(`🚪 自动门点映射初始化完成: 共找到 ${autoDoorCount} 个自动门点`);
}
/**
@ -169,7 +173,7 @@ export class AutoDoorService {
// 注意:这里不需要创建完整的模拟数据对象,只需要更新状态
if (enableLogging) {
console.log(`🚪 自动门点状态更新: ${newStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
// console.log(`🚪 自动门点状态更新: ${newStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
}
// 更新编辑器中的自动门点状态
@ -233,7 +237,7 @@ export class AutoDoorService {
if (this.editorService) {
this.editorService.updateAutoDoorByDeviceId(deviceId, status, true);
console.log(`🚪 手动设置自动门点状态: ${status === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
// console.log(`🚪 手动设置自动门点状态: ${status === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
}
}
@ -250,7 +254,7 @@ export class AutoDoorService {
* @param data WebSocket推送的数据
*/
handleWebSocketData(data: AutoDoorWebSocketData): void {
const { label: deviceId, deviceStatus, active = true } = data;
const { label: deviceId, deviceStatus, active = true, isConnected = true } = data;
if (!deviceId || deviceStatus === undefined) {
console.warn('⚠️ 自动门点数据格式不正确', data);
@ -258,11 +262,11 @@ export class AutoDoorService {
}
// 缓存最新数据
this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active });
this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active, isConnected });
console.log(
`🚪 收到自动门点WebSocket数据: ${deviceStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`,
);
// console.log(
// `🚪 收到自动门点WebSocket数据: ${deviceStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`,
// );
}
/**
@ -281,17 +285,25 @@ export class AutoDoorService {
const [deviceId, data] = entry;
this.latestAutoDoorData.delete(deviceId);
// 使用映射缓存快速查找点位ID
const pointId = this.deviceIdToPointIdMap.get(data.deviceId);
// 使用映射缓存快速查找点位ID列表
const pointIds = this.deviceIdToPointIdMap.get(data.deviceId);
if (!pointId) {
if (!pointIds || pointIds.length === 0) {
console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,可能需要刷新映射`);
continue;
}
// 更新自动门点状态使用pointId直接更新避免查找
// 更新该设备对应的所有自动门点状态
if (this.editorService) {
this.editorService.updateAutoDoorByDeviceId(data.deviceId, data.deviceStatus, data.active, pointId);
for (const pid of pointIds) {
this.editorService.updateAutoDoorByDeviceId(
data.deviceId,
data.deviceStatus,
data.isConnected,
data.active,
pid,
);
}
}
}

View File

@ -866,7 +866,6 @@ export class EditorService extends Meta2d {
public updatePointBorderColor(pointId: string, color: string): void {
const pen = this.getPenById(pointId);
if (!pen || pen.name !== 'point') return;
this.updatePen(pointId, { statusStyle: color }, false);
}
@ -874,10 +873,17 @@ export class EditorService extends Meta2d {
* ID更新自动门点状态
* @param deviceId ID
* @param deviceStatus 0=1=
* @param isConnected true=false=
* @param active
* @param pointId ID使
*/
public updateAutoDoorByDeviceId(deviceId: string, deviceStatus: number, active = true, pointId?: string): void {
public updateAutoDoorByDeviceId(
deviceId: string,
deviceStatus: number,
isConnected: boolean,
active = true,
pointId?: string,
): void {
let autoDoorPointId = pointId;
// 如果没有提供pointId则通过deviceId查找
@ -912,6 +918,7 @@ export class EditorService extends Meta2d {
point: {
...autoDoorPoint.point,
deviceStatus,
isConnected,
active,
},
},
@ -1445,31 +1452,39 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
const theme = sTheme.editor;
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, deviceStatus, active: pointActive } = pen.point ?? {};
const { type, isConnected, deviceStatus, active: pointActive } = pen.point ?? {};
const { label = '', statusStyle } = pen ?? {};
ctx.save();
// 为自动门点绘制光圈
if (type === MapPointType. && pointActive && deviceStatus !== undefined) {
const ox = x + w / 2;
const oy = y + h / 2;
console.log(ox, oy);
const haloRadius = Math.max(w, h) / 2 + 6; // 光圈半径比点位本身大一些
// 自动门点:根据连接与开关状态绘制矩形光圈(无边框)
if (type === MapPointType. && pointActive) {
// 让光圈随点位尺寸等比缩放,避免缩放画布时视觉上变大
const base = Math.min(w, h);
const padding = Math.max(2, Math.min(10, base * 0.2));
const rx = x - padding;
const ry = y - padding;
const rw = w + padding * 2;
const rh = h + padding * 2;
ctx.ellipse(ox, oy, haloRadius, haloRadius, 0, 0, Math.PI * 2);
// 使用与点位相同的圆角半径,使观感统一
ctx.beginPath();
ctx.roundRect(rx, ry, rw, rh, r);
// 根据设备状态选择颜色0=关门红色1=开门(蓝色)
if (deviceStatus === 0) {
ctx.fillStyle = get(theme, 'autoDoor.fill-closed') ?? '#FF4D4F33';
ctx.strokeStyle = get(theme, 'autoDoor.stroke-closed') ?? '#FF4D4F99';
if (isConnected === false) {
// 未连接:深红色实心,不描边
ctx.fillStyle = '#fe5a5ae0';
} else {
ctx.fillStyle = get(theme, 'autoDoor.fill-open') ?? '#1890FF33';
ctx.strokeStyle = get(theme, 'autoDoor.stroke-open') ?? '#1890FF99';
// 已连接根据门状态显示颜色0=关门-浅红1=开门-蓝色)
if (deviceStatus === 0) {
ctx.fillStyle = '#cddc39';
} else {
ctx.fillStyle = '#1890FF';
}
}
ctx.fill();
ctx.stroke();
// 重置路径,避免后续对点位边框的 stroke 影响到光圈路径
ctx.beginPath();
}
switch (type) {
@ -1478,6 +1493,7 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
case MapPointType.:
case MapPointType.:
case MapPointType.:
case MapPointType.:
ctx.beginPath();
ctx.moveTo(x + w / 2 - r, y + r);
ctx.arcTo(x + w / 2, y, x + w - r, y + h / 2 - r, r);

File diff suppressed because it is too large Load Diff