feat: 扩展机器人状态信息,新增充电、载货和不接单状态,更新相关组件以显示状态图标,优化机器人信息同步逻辑
This commit is contained in:
parent
911a9fbe2f
commit
c5064b189c
@ -9,6 +9,19 @@ export interface RobotGroup {
|
||||
robots?: Array<string>; // 机器人列表
|
||||
}
|
||||
|
||||
export type RobotPen = Pen & {
|
||||
robot: {
|
||||
type: RobotType;
|
||||
active?: boolean;
|
||||
isWaring?: boolean;
|
||||
isFault?: boolean;
|
||||
isCharging?: boolean; // 是否充电中
|
||||
isCarrying?: boolean; // 是否载货
|
||||
isNotAcceptingOrders?: boolean; // 是否不接单
|
||||
path?: { x: number; y: number }[];
|
||||
angle?: number;
|
||||
};
|
||||
};
|
||||
export interface RobotInfo {
|
||||
gid?: string; // 机器人组id
|
||||
id: string; // 机器人id
|
||||
@ -24,6 +37,9 @@ export interface RobotInfo {
|
||||
canControl?: boolean; // 控制状态
|
||||
targetPoint?: string; // 目标点位(名称)
|
||||
isLoading?: 0 | 1; // 载货状态:1载货,0空载(实时数据透传)
|
||||
isCharging?: boolean; // 是否充电中
|
||||
isCarrying?: boolean; // 是否载货
|
||||
isNotAcceptingOrders?: boolean; // 是否不接单
|
||||
}
|
||||
|
||||
export interface RobotDetail extends RobotInfo {
|
||||
@ -45,6 +61,9 @@ export interface RobotRealtimeInfo extends RobotInfo {
|
||||
path?: Array<Point>; // 规划路径
|
||||
isWaring?: boolean; // 是否告警
|
||||
isFault?: boolean; // 是否故障
|
||||
isCharging?: boolean; // 是否充电中
|
||||
isCarrying?: boolean; // 是否载货
|
||||
isNotAcceptingOrders?: boolean; // 是否不接单
|
||||
}
|
||||
|
||||
// AMR Redis状态接口 - 更新为实际返回的数据结构
|
||||
|
BIN
src/assets/icons/png/cargo.png
Normal file
BIN
src/assets/icons/png/cargo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 279 B |
BIN
src/assets/icons/png/charging.png
Normal file
BIN
src/assets/icons/png/charging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 491 B |
BIN
src/assets/icons/png/notAcceptingOrders.png
Normal file
BIN
src/assets/icons/png/notAcceptingOrders.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 888 B |
@ -16,6 +16,11 @@
|
||||
<div class="robot-status" :style="{ color: getRobotStatusColor((robotInfo.state as any) || 'offline') }">
|
||||
{{ getRobotStatusText((robotInfo.state as any) || 'offline') }}
|
||||
</div>
|
||||
<div class="robot-extra-statuses">
|
||||
<span v-if="robotInfo.isCarrying" class="extra-status-tag">载货中</span>
|
||||
<span v-if="robotInfo.isNotAcceptingOrders" class="extra-status-tag">不接单</span>
|
||||
<span v-if="robotInfo.isCharging" class="extra-status-tag">充电中</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="robot-details">
|
||||
@ -326,6 +331,23 @@ onUnmounted(() => {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.robot-extra-statuses {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.extra-status-tag {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
padding: 2px 6px;
|
||||
border-radius: 8px;
|
||||
background-color: #e9ecef;
|
||||
color: #495057;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.robot-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
|
@ -100,10 +100,28 @@ const monitorScene = async () => {
|
||||
|
||||
// 先更新所有机器人的状态信息,包括光圈渲染所需的状态
|
||||
updates.forEach(({ id, data }) => {
|
||||
const { x, y, active, angle, path: points, isWaring, isFault, ...rest } = data;
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
active,
|
||||
angle,
|
||||
path: points,
|
||||
isWaring,
|
||||
isFault,
|
||||
isCharging = 1,
|
||||
isCarrying = 1,
|
||||
isNotAcceptingOrders = 1,
|
||||
...rest
|
||||
} = data;
|
||||
|
||||
// 更新机器人基本信息
|
||||
editor.value?.updateRobot(id, rest);
|
||||
// 同步扩展状态(充电/载货/不接单)到机器人信息表,便于服务内读取
|
||||
editor.value?.updateRobot(id, {
|
||||
...rest,
|
||||
isCharging: isCharging as any as boolean,
|
||||
isCarrying: isCarrying as any as boolean,
|
||||
isNotAcceptingOrders: isNotAcceptingOrders as any as boolean,
|
||||
});
|
||||
|
||||
// 处理路径坐标转换,参考refreshRobot方法的逻辑
|
||||
let processedPath: Array<{ x: number; y: number }> | undefined;
|
||||
@ -115,7 +133,16 @@ const monitorScene = async () => {
|
||||
}
|
||||
|
||||
// 更新机器人状态,触发光圈渲染
|
||||
const robotState = { active, isWaring, isFault, path: processedPath, angle };
|
||||
const robotState = {
|
||||
active,
|
||||
isWaring,
|
||||
isFault,
|
||||
path: processedPath,
|
||||
angle,
|
||||
isCharging,
|
||||
isCarrying,
|
||||
isNotAcceptingOrders,
|
||||
};
|
||||
if (Object.values(robotState).some((v) => v !== undefined)) {
|
||||
editor.value?.setValue({ id, robot: robotState }, { render: false, history: false, doEvent: false });
|
||||
}
|
||||
@ -140,6 +167,11 @@ const monitorScene = async () => {
|
||||
editor.value?.setValue(update, { render: false, history: false, doEvent: false });
|
||||
});
|
||||
|
||||
// 更新状态覆盖图标(按优先级:载货 > 不接单 > 充电),大小为机器人图片一半并居中叠加
|
||||
updates.forEach(({ id }) => {
|
||||
editor.value?.updateRobotStatusOverlay?.(id, false);
|
||||
});
|
||||
|
||||
// 批量更新完成后,统一渲染一次
|
||||
editor.value?.render();
|
||||
};
|
||||
|
@ -27,6 +27,10 @@ import { clone, get, isEmpty, isNil, isString, nth, pick, remove, some } from 'l
|
||||
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
// 机器人状态覆盖图标
|
||||
import cargoIcon from '../assets/icons/png/cargo.png';
|
||||
import chargingIcon from '../assets/icons/png/charging.png';
|
||||
import notAcceptingOrdersIcon from '../assets/icons/png/notAcceptingOrders.png';
|
||||
import { AreaOperationService } from './area-operation.service';
|
||||
import { BinTaskManagerService } from './bintask-manager.service';
|
||||
import colorConfig from './color/color-config.service';
|
||||
@ -1086,6 +1090,8 @@ export class EditorService extends Meta2d {
|
||||
locked: LockState.Disable,
|
||||
};
|
||||
await this.addPen(pen, false, true, true);
|
||||
// 初始化时创建/同步一次状态覆盖图标(默认隐藏或根据当前状态显示)
|
||||
this.updateRobotStatusOverlay(id, false);
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1147,6 +1153,8 @@ export class EditorService extends Meta2d {
|
||||
},
|
||||
{ render: true, history: false, doEvent: false },
|
||||
);
|
||||
// 同步状态图标尺寸/位置
|
||||
this.updateRobotStatusOverlay(id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1215,6 +1223,114 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 计算机器人状态覆盖图标(优先级:载货 > 不接单 > 充电)
|
||||
*/
|
||||
#getRobotStatusIcon(pen?: MapPen): string | null {
|
||||
if (!pen) return null;
|
||||
// 兼容:状态可能存放在 pen.robot 或 机器人信息表中
|
||||
const r1: any = pen.robot ?? {};
|
||||
const r2 = this.getRobotById(pen.id || '');
|
||||
const toBool = (v: any) => v === true || v === 1 || v === '1';
|
||||
const isCarrying = toBool(r1?.isCarrying ?? r2?.isCarrying);
|
||||
const isNotAcceptingOrders = toBool(r1?.isNotAcceptingOrders ?? r2?.isNotAcceptingOrders);
|
||||
const isCharging = toBool(r1?.isCharging ?? r2?.isCharging);
|
||||
|
||||
if (isCarrying) return cargoIcon;
|
||||
if (isNotAcceptingOrders) return notAcceptingOrdersIcon;
|
||||
if (isCharging) return chargingIcon;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个机器人的状态覆盖图标位置/尺寸/显隐
|
||||
* @param id 机器人ID
|
||||
* @param render 是否立即渲染
|
||||
*/
|
||||
public updateRobotStatusOverlay(id: string, render = false): void {
|
||||
const pen = this.getPenById(id);
|
||||
if (!pen) return;
|
||||
|
||||
const icon = this.#getRobotStatusIcon(pen);
|
||||
const robotVisible = pen.visible !== false;
|
||||
|
||||
// 计算覆盖图标的尺寸(为机器人图片的一半)
|
||||
const baseW = (pen as any).iconWidth ?? 42;
|
||||
const baseH = (pen as any).iconHeight ?? 76;
|
||||
const oW = Math.max(8, Math.floor(baseW * 0.5));
|
||||
const oH = Math.max(8, Math.floor(baseH * 0.5));
|
||||
|
||||
// 以机器人中心为基准,做一个轻微向下的偏移,并随机器人一起旋转
|
||||
const rect = this.getPenRect(pen);
|
||||
const wr: any = (pen as any).calculative?.worldRect;
|
||||
const deg: number = (wr?.rotate ?? 0) as number;
|
||||
const theta = (deg * Math.PI) / 180;
|
||||
const cx = rect.x + rect.width / 2;
|
||||
const cy = rect.y + rect.height / 2;
|
||||
const iconTop = (pen as any).iconTop ?? 0;
|
||||
// 在本地坐标中的偏移(以机器人中心为原点,y向下为正)
|
||||
const localDx = 0;
|
||||
const localDy = iconTop +16;
|
||||
// 旋转后的偏移
|
||||
const rotDx = localDx * Math.cos(theta) - localDy * Math.sin(theta);
|
||||
const rotDy = localDx * Math.sin(theta) + localDy * Math.cos(theta);
|
||||
// 覆盖图标中心点
|
||||
const icx = cx + rotDx;
|
||||
const icy = cy + rotDy;
|
||||
const ox = icx - oW / 2;
|
||||
const oy = icy - oH / 2;
|
||||
|
||||
const overlayId = `robot-status-${id}`;
|
||||
const exist = this.getPenById(overlayId);
|
||||
|
||||
if (!icon || !robotVisible) {
|
||||
if (exist) {
|
||||
this.setValue({ id: overlayId, visible: false }, { render, history: false, doEvent: false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已存在覆盖图标,更新其属性;否则创建新的 image 图元
|
||||
if (exist) {
|
||||
this.setValue(
|
||||
{
|
||||
id: overlayId,
|
||||
image: icon,
|
||||
x: ox,
|
||||
y: oy,
|
||||
width: oW,
|
||||
height: oH,
|
||||
rotate: deg,
|
||||
visible: true,
|
||||
locked: LockState.Disable,
|
||||
},
|
||||
{ render: false, history: false, doEvent: false },
|
||||
);
|
||||
// 置顶,保证覆盖在机器人之上
|
||||
this.top([exist]);
|
||||
if (render) this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
const overlayPen: MapPen = {
|
||||
id: overlayId,
|
||||
name: 'image',
|
||||
tags: ['robot-status'],
|
||||
x: ox,
|
||||
y: oy,
|
||||
width: oW,
|
||||
height: oH,
|
||||
image: icon,
|
||||
rotate: deg,
|
||||
canvasLayer: CanvasLayer.CanvasImage,
|
||||
locked: LockState.Disable,
|
||||
visible: true,
|
||||
} as any;
|
||||
this.addPen(overlayPen, false, false, true);
|
||||
this.top([overlayPen]);
|
||||
if (render) this.render();
|
||||
}
|
||||
|
||||
//#region 点位
|
||||
/** 画布上所有点位对象列表,响应式更新 */
|
||||
public readonly points = useObservable<MapPen[], MapPen[]>(
|
||||
|
@ -26,6 +26,7 @@ export class LayerManagerService {
|
||||
const routes = this.editor.find('route');
|
||||
const points = this.editor.find('point');
|
||||
const robots = this.editor.find('robot');
|
||||
const images = this.editor.find('image');
|
||||
const storageBackgrounds = this.editor.find('storage-background');
|
||||
const storageLocations = this.editor.find('storage-location,storage-more');
|
||||
|
||||
@ -58,6 +59,12 @@ export class LayerManagerService {
|
||||
if (robots.length > 0) {
|
||||
this.editor.top(robots);
|
||||
}
|
||||
|
||||
// 将机器人状态覆盖图标置于最顶层(在机器人之上)
|
||||
const robotStatusOverlays = images.filter((p: any) => p?.tags?.includes('robot-status'));
|
||||
if (robotStatusOverlays.length > 0) {
|
||||
this.editor.top(robotStatusOverlays);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user