Compare commits
9 Commits
master
...
feature/ph
Author | SHA1 | Date | |
---|---|---|---|
e2753c65a3 | |||
4405702381 | |||
5f75ca2fa2 | |||
318e69cfaf | |||
3ed67542bf | |||
ca252acff9 | |||
b0cf3a896d | |||
7156ee6ddf | |||
95f7894328 |
@ -18,6 +18,8 @@ export enum MapPointType {
|
||||
临时避让点,
|
||||
/** 库区点 - 仓储作业区域 */
|
||||
库区点,
|
||||
/** 不可避让点 - 机器人不可避让的点位 */
|
||||
不可避让点 = 7,
|
||||
/** 密集库区点 - 密集库区点位 */
|
||||
// 密集库区点,
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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; // 机器人中心X坐标,默认37
|
||||
const cy = y || 37; // 机器人中心Y坐标,默认37
|
||||
@ -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>
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
1672
地图互转工具.html
1672
地图互转工具.html
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user