feat: 添加库位管理功能,支持库位的创建、更新和删除,优化库位渲染和状态显示
This commit is contained in:
parent
510fb7e5d0
commit
0ffb170ec8
@ -11,6 +11,7 @@ export interface MapPen extends Pen {
|
||||
route?: MapRouteInfo; // 线路信息
|
||||
area?: MapAreaInfo; // 区域信息
|
||||
robot?: MapRobotInfo; // 实时机器人信息
|
||||
storageLocation?: MapStorageLocationInfo; // 库位信息
|
||||
|
||||
attrs?: Record<string, unknown>; // 额外属性
|
||||
activeAttrs?: Array<string>; // 已激活的额外属性
|
||||
@ -61,6 +62,17 @@ export interface MapAreaInfo {
|
||||
export type MapRobotInfo = Pick<RobotRealtimeInfo, 'type' | 'active' | 'path' | 'isWaring' | 'isFault'>;
|
||||
//#endregion
|
||||
|
||||
//#region 库位
|
||||
export interface MapStorageLocationInfo {
|
||||
pointId: string; // 所属动作点ID
|
||||
locationName: string; // 库位名称
|
||||
index: number; // 在栅格中的索引位置
|
||||
occupied?: boolean; // 是否被占用
|
||||
locked?: boolean; // 是否被锁定
|
||||
overflow?: number; // 超出显示的库位数量(仅"更多"按钮使用)
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export type Point = Record<'x' | 'y', number>;
|
||||
export type Rect = Record<'x' | 'y' | 'width' | 'height', number>;
|
||||
export type AnchorPosition = 't' | 'b' | 'l' | 'r';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { MapPen } from '@api/map';
|
||||
import { LockState } from '@meta2d/core';
|
||||
|
||||
// 预加载锁定图标(仅一次)
|
||||
const lockedIcon = new Image();
|
||||
@ -37,7 +38,7 @@ export function drawStorageGrid(
|
||||
|
||||
// 尺寸与间距(世界坐标)
|
||||
const base = Math.min(w, h);
|
||||
const cell = Math.max(6, Math.min(14, base * 0.35));
|
||||
const cell = Math.max(12, Math.min(24, base * 0.60));
|
||||
const gap = Math.max(2, Math.min(6, base * 0.08));
|
||||
const gridCols = 3;
|
||||
const gridRows = 2;
|
||||
@ -118,3 +119,273 @@ export function drawStorageGrid(
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制库位的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 库位图形对象
|
||||
*/
|
||||
export function drawStorageLocation(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
const { occupied = false, locked = false } = pen.storageLocation ?? {};
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 绘制圆角矩形
|
||||
const r = Math.min(4, w * 0.3);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.arcTo(x, y, x + r, y, r);
|
||||
ctx.closePath();
|
||||
|
||||
// 填充颜色:占用为红色,否则默认灰底
|
||||
ctx.fillStyle = occupied ? '#ff4d4f' : '#f5f5f5';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#999999';
|
||||
ctx.stroke();
|
||||
|
||||
// 如果锁定,绘制锁图标
|
||||
if (locked && lockedIcon.complete && lockedIcon.naturalWidth > 0) {
|
||||
const pad = Math.max(1, Math.floor(w * 0.1));
|
||||
// 图标充满整个库位,留少量边距
|
||||
const iconSize = Math.max(0, w - 2 * pad);
|
||||
const iconX = x + pad;
|
||||
const iconY = y + pad;
|
||||
|
||||
// 将绘制范围裁剪到当前库位,确保不会溢出
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
const r = Math.min(4, w * 0.3);
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.arcTo(x, y, x + r, y, r);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
|
||||
// 绘制锁定图标
|
||||
ctx.drawImage(lockedIcon, iconX, iconY, iconSize, iconSize);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制"更多"库位按钮的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen "更多"按钮图形对象
|
||||
*/
|
||||
export function drawStorageMore(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 绘制圆角矩形
|
||||
const r = Math.min(4, w * 0.3);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.arcTo(x, y, x + r, y, r);
|
||||
ctx.closePath();
|
||||
|
||||
// 填充颜色和边框
|
||||
ctx.fillStyle = '#e6f4ff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#1677ff';
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制文字
|
||||
ctx.fillStyle = '#1677ff';
|
||||
ctx.font = `${Math.floor(w * 0.6)}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(pen.text || '+0', x + w / 2, y + h / 2);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制库位区域背景的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 背景矩形图形对象
|
||||
*/
|
||||
export function drawStorageBackground(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 绘制圆角矩形背景
|
||||
const r = Math.min(6, w * 0.1);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + r, y);
|
||||
ctx.lineTo(x + w - r, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + r, r);
|
||||
ctx.lineTo(x + w, y + h - r);
|
||||
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
||||
ctx.lineTo(x + r, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - r, r);
|
||||
ctx.lineTo(x, y + r);
|
||||
ctx.arcTo(x, y, x + r, y, r);
|
||||
ctx.closePath();
|
||||
|
||||
// 填充半透明背景
|
||||
ctx.fillStyle = '#00000022';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#999999';
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库位pen对象的工厂函数
|
||||
* @param pointId 点位ID
|
||||
* @param storageLocations 库位列表
|
||||
* @param pointRect 点位矩形信息
|
||||
* @param storageStateMap 库位状态映射
|
||||
* @returns 库位相关的pen对象数组
|
||||
*/
|
||||
export function createStorageLocationPens(
|
||||
pointId: string,
|
||||
storageLocations: string[],
|
||||
pointRect: { x: number; y: number; width: number; height: number },
|
||||
storageStateMap: Map<string, Map<string, { occupied?: boolean; locked?: boolean }>>
|
||||
): MapPen[] {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pointRect;
|
||||
|
||||
// 库位栅格参数
|
||||
const base = Math.min(w, h);
|
||||
const cell = Math.max(12, Math.min(24, base * 0.60));
|
||||
const gap = Math.max(2, Math.min(6, base * 0.08));
|
||||
const gridCols = 3;
|
||||
const gridRows = 2;
|
||||
const gridH = gridRows * cell + (gridRows - 1) * gap;
|
||||
|
||||
// 右上角位置 - getPointRect返回的是中心点坐标,需要转换为左上角坐标
|
||||
const outer = gap;
|
||||
const gx = x + w / 2 + outer;
|
||||
const gy = y - h / 2 - gridH - outer;
|
||||
|
||||
const pens: MapPen[] = [];
|
||||
|
||||
// 创建库位区域背景矩形
|
||||
const backgroundPen: MapPen = {
|
||||
id: `storage-bg-${pointId}`,
|
||||
name: 'storage-background',
|
||||
tags: ['storage-background', `point-${pointId}`],
|
||||
x: gx - gap * 0.5,
|
||||
y: gy - gap * 0.5,
|
||||
width: gridCols * cell + (gridCols - 1) * gap + gap,
|
||||
height: gridRows * cell + (gridRows - 1) * gap + gap,
|
||||
lineWidth: 1,
|
||||
background: '#00000022',
|
||||
color: '#999999',
|
||||
locked: LockState.DisableEdit,
|
||||
visible: true,
|
||||
borderRadius: Math.min(6, cell * 0.4),
|
||||
};
|
||||
pens.push(backgroundPen);
|
||||
|
||||
// 创建库位pen对象
|
||||
storageLocations.slice(0, gridCols * gridRows).forEach((locationName, i) => {
|
||||
const r = Math.floor(i / gridCols);
|
||||
const c = i % gridCols;
|
||||
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置
|
||||
const relativeX = c * (cell + gap) + cell / 2;
|
||||
const relativeY = r * (cell + gap) + cell / 2;
|
||||
const cx = gx - gap * 0.5 + relativeX;
|
||||
const cy = gy - gap * 0.5 + relativeY;
|
||||
|
||||
// 从storageStateMap获取库位状态
|
||||
const inner = storageStateMap.get(pointId);
|
||||
const state = inner?.get(locationName) || { occupied: false, locked: false };
|
||||
const occupied = !!state.occupied;
|
||||
const locked = !!state.locked;
|
||||
|
||||
const storagePen: MapPen = {
|
||||
id: `storage-${pointId}-${i}`,
|
||||
name: 'storage-location',
|
||||
tags: ['storage-location', `point-${pointId}`],
|
||||
x: cx,
|
||||
y: cy,
|
||||
width: cell,
|
||||
height: cell,
|
||||
lineWidth: 1,
|
||||
background: occupied ? '#ff4d4f' : '#f5f5f5',
|
||||
color: '#999999',
|
||||
locked: LockState.DisableEdit,
|
||||
visible: true,
|
||||
borderRadius: Math.min(4, cell * 0.3),
|
||||
storageLocation: {
|
||||
pointId,
|
||||
locationName,
|
||||
index: i,
|
||||
occupied,
|
||||
locked,
|
||||
},
|
||||
};
|
||||
|
||||
pens.push(storagePen);
|
||||
});
|
||||
|
||||
// 如果有超出显示的库位,创建"更多"按钮
|
||||
const overflow = Math.max(0, storageLocations.length - gridCols * gridRows);
|
||||
if (overflow > 0) {
|
||||
const i = gridCols * gridRows - 1;
|
||||
const r = Math.floor(i / gridCols);
|
||||
const c = i % gridCols;
|
||||
// 计算库位在背景矩形内的相对位置,然后加上背景矩形的起始位置
|
||||
const relativeX = c * (cell + gap) + cell / 2;
|
||||
const relativeY = r * (cell + gap) + cell / 2;
|
||||
const cx = gx - gap * 0.5 + relativeX;
|
||||
const cy = gy - gap * 0.5 + relativeY;
|
||||
|
||||
const morePen: MapPen = {
|
||||
id: `storage-more-${pointId}`,
|
||||
name: 'storage-more',
|
||||
tags: ['storage-more', `point-${pointId}`],
|
||||
x: cx,
|
||||
y: cy,
|
||||
width: cell,
|
||||
height: cell,
|
||||
lineWidth: 1,
|
||||
background: '#e6f4ff',
|
||||
color: '#1677ff',
|
||||
locked: LockState.DisableEdit,
|
||||
visible: true,
|
||||
text: `+${overflow}`,
|
||||
fontSize: Math.floor(cell * 0.6),
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
storageLocation: {
|
||||
pointId,
|
||||
locationName: 'more',
|
||||
index: i,
|
||||
overflow,
|
||||
},
|
||||
};
|
||||
|
||||
pens.push(morePen);
|
||||
}
|
||||
|
||||
return pens;
|
||||
}
|
||||
|
@ -30,9 +30,13 @@ import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from '
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
import { AreaOperationService } from './area-operation.service';
|
||||
import { drawStorageGrid } from './draw/storage-location-drawer';
|
||||
import {
|
||||
drawStorageBackground,
|
||||
drawStorageLocation,
|
||||
drawStorageMore,
|
||||
} from './draw/storage-location-drawer';
|
||||
import { LayerManagerService } from './layer-manager.service';
|
||||
import { storageStateMap } from './storage-location.service';
|
||||
import { createStorageLocationUpdater,StorageLocationService } from './storage-location.service';
|
||||
|
||||
/**
|
||||
* 场景编辑器服务类
|
||||
@ -48,6 +52,8 @@ import { storageStateMap } from './storage-location.service';
|
||||
export class EditorService extends Meta2d {
|
||||
/** 图层管理服务实例 */
|
||||
private readonly layerManager: LayerManagerService;
|
||||
/** 库位服务实例 */
|
||||
private readonly storageLocationService: StorageLocationService;
|
||||
|
||||
/** 区域操作服务实例 */
|
||||
private readonly areaOperationService!: AreaOperationService;
|
||||
@ -85,6 +91,9 @@ export class EditorService extends Meta2d {
|
||||
// 确保正确的层级顺序:路线 < 点位 < 机器人
|
||||
this.#ensureCorrectLayerOrder();
|
||||
|
||||
// 为所有动作点创建库位pen对象
|
||||
this.createAllStorageLocationPens();
|
||||
|
||||
this.store.historyIndex = undefined;
|
||||
this.store.histories = [];
|
||||
// this.scale(scale);与xd 自定义缩放冲突,暂时去掉
|
||||
@ -871,6 +880,78 @@ export class EditorService extends Meta2d {
|
||||
this.updatePen(pointId, { statusStyle: color }, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为动作点创建库位pen对象
|
||||
* @param pointId 动作点ID
|
||||
* @param storageLocations 库位名称列表
|
||||
*/
|
||||
public createStorageLocationPens(pointId: string, storageLocations: string[]): void {
|
||||
this.storageLocationService?.create(pointId, storageLocations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除动作点的所有库位pen对象
|
||||
* @param pointId 动作点ID
|
||||
*/
|
||||
public removeStorageLocationPens(pointId: string): void {
|
||||
this.storageLocationService?.delete(pointId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库位
|
||||
* @param pointId 动作点ID
|
||||
* @param storageLocations 库位名称列表
|
||||
*/
|
||||
public createStorageLocation(pointId: string, storageLocations: string[]): void {
|
||||
this.storageLocationService?.create(pointId, storageLocations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新库位状态
|
||||
* @param pointId 动作点ID
|
||||
* @param updates 更新操作,可以是单个库位或批量更新
|
||||
* @param state 单个库位状态(当updates为字符串时使用)
|
||||
*/
|
||||
public updateStorageLocation(
|
||||
pointId: string,
|
||||
updates: string | Record<string, { occupied?: boolean; locked?: boolean }>,
|
||||
state?: { occupied?: boolean; locked?: boolean }
|
||||
): void {
|
||||
this.storageLocationService?.update(pointId, updates, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除库位
|
||||
* @param pointId 动作点ID
|
||||
*/
|
||||
public deleteStorageLocation(pointId: string): void {
|
||||
this.storageLocationService?.delete(pointId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取库位更新器实例
|
||||
* @returns 库位更新器实例
|
||||
*/
|
||||
public getStorageLocationUpdater() {
|
||||
if (!this.storageLocationService) {
|
||||
throw new Error('StorageLocationService 未初始化');
|
||||
}
|
||||
return createStorageLocationUpdater(this.storageLocationService);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为所有动作点创建库位pen对象
|
||||
* 用于初始化或刷新所有动作点的库位显示
|
||||
*/
|
||||
public createAllStorageLocationPens(): void {
|
||||
this.storageLocationService?.createAll();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备ID更新自动门点状态
|
||||
* @param deviceId 设备ID
|
||||
@ -1077,6 +1158,11 @@ export class EditorService extends Meta2d {
|
||||
|
||||
// 将新创建的点位移到最上层
|
||||
this.layerManager.adjustElementLayer(addedPen);
|
||||
|
||||
// 如果是动作点且有库位信息,创建库位pen对象
|
||||
if (type === MapPointType.动作点 && pointInfo.associatedStorageLocations?.length) {
|
||||
this.createStorageLocationPens(addedPen.id!, pointInfo.associatedStorageLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public updatePoint(id: string, info: Partial<MapPointInfo>): void {
|
||||
@ -1084,6 +1170,11 @@ export class EditorService extends Meta2d {
|
||||
if (!point?.type) return;
|
||||
const o = { ...point, ...info };
|
||||
this.setValue({ id, point: o }, { render: true, history: true, doEvent: true });
|
||||
|
||||
// 如果是动作点且库位信息发生变化,重新创建库位pen对象
|
||||
if (point.type === MapPointType.动作点 && info.associatedStorageLocations) {
|
||||
this.createStorageLocationPens(id, info.associatedStorageLocations);
|
||||
}
|
||||
}
|
||||
public changePointType(id: string, type: MapPointType): void {
|
||||
const pen = this.getPenById(id);
|
||||
@ -1292,6 +1383,9 @@ export class EditorService extends Meta2d {
|
||||
// 初始化区域操作服务
|
||||
this.areaOperationService = new AreaOperationService();
|
||||
|
||||
// 初始化库位服务
|
||||
this.storageLocationService = new StorageLocationService(this, '');
|
||||
|
||||
// 禁用第6个子元素的拖放功能
|
||||
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
||||
// 监听所有画布事件
|
||||
@ -1381,6 +1475,10 @@ export class EditorService extends Meta2d {
|
||||
case 'mouseup':
|
||||
this.#mouse$$.next({ type: e, value: pick(this.getPenRect(v), 'x', 'y') });
|
||||
break;
|
||||
case 'contextmenu':
|
||||
// 处理右键菜单
|
||||
console.log('右键事件触发:', { event: e, pen: v });
|
||||
break;
|
||||
// 监听区域调整大小事件
|
||||
case 'resizePens': {
|
||||
// resizePens 事件的目标可能是 undefined,需要从 store.active 获取
|
||||
@ -1437,7 +1535,15 @@ export class EditorService extends Meta2d {
|
||||
|
||||
#register() {
|
||||
this.register({ line: () => new Path2D() });
|
||||
this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot });
|
||||
this.registerCanvasDraw({
|
||||
point: drawPoint,
|
||||
line: drawLine,
|
||||
area: drawArea,
|
||||
robot: drawRobot,
|
||||
'storage-location': drawStorageLocation,
|
||||
'storage-more': drawStorageMore,
|
||||
'storage-background': drawStorageBackground
|
||||
});
|
||||
this.registerAnchors({ point: anchorPoint });
|
||||
this.addDrawLineFn('bezier2', lineBezier2);
|
||||
this.addDrawLineFn('bezier3', lineBezier3);
|
||||
@ -1547,20 +1653,21 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
|
||||
|
||||
// 库位2x3栅格:动作点在画布上直接绘制(静态数据)
|
||||
if (type === MapPointType.动作点) {
|
||||
drawStorageGrid(ctx, pen, {
|
||||
fontFamily,
|
||||
stateResolver: (penId: string, layerName: string) => {
|
||||
const inner = storageStateMap.get(penId);
|
||||
if (!inner) {
|
||||
return {};
|
||||
}
|
||||
const state = inner.get(layerName);
|
||||
// 注意:库位现在通过动态pen对象渲染,不再使用静态绘制
|
||||
// if (type === MapPointType.动作点) {
|
||||
// drawStorageGrid(ctx, pen, {
|
||||
// fontFamily,
|
||||
// stateResolver: (penId: string, layerName: string) => {
|
||||
// const inner = storageStateMap.get(penId);
|
||||
// if (!inner) {
|
||||
// return {};
|
||||
// }
|
||||
// const state = inner.get(layerName);
|
||||
|
||||
return state ?? {};
|
||||
},
|
||||
});
|
||||
}
|
||||
// return state ?? {};
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
ctx.restore();
|
||||
}
|
||||
/**
|
||||
@ -1824,6 +1931,7 @@ function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 辅助函数
|
||||
|
@ -26,6 +26,8 @@ export class LayerManagerService {
|
||||
const routes = this.editor.find('route');
|
||||
const points = this.editor.find('point');
|
||||
const robots = this.editor.find('robot');
|
||||
const storageBackgrounds = this.editor.find('storage-background');
|
||||
const storageLocations = this.editor.find('storage-location,storage-more');
|
||||
|
||||
// 将区域移到最底层(作为背景)
|
||||
if (areas.length > 0) {
|
||||
@ -37,6 +39,16 @@ export class LayerManagerService {
|
||||
this.editor.top(routes);
|
||||
}
|
||||
|
||||
// 将库位背景移到点位下方
|
||||
if (storageBackgrounds.length > 0) {
|
||||
this.editor.top(storageBackgrounds);
|
||||
}
|
||||
|
||||
// 将库位移到点位下方但在背景上方
|
||||
if (storageLocations.length > 0) {
|
||||
this.editor.top(storageLocations);
|
||||
}
|
||||
|
||||
// 将点位和机器人都移到最上层(在路线之上)
|
||||
const topElements = [...points, ...robots];
|
||||
if (topElements.length > 0) {
|
||||
@ -111,6 +123,12 @@ export class LayerManagerService {
|
||||
case 'robot':
|
||||
this.ensureRobotsAtTop();
|
||||
break;
|
||||
case 'storage-background':
|
||||
case 'storage-location':
|
||||
case 'storage-more':
|
||||
// 库位相关元素,确保在正确的层级
|
||||
this.ensureCorrectLayerOrder();
|
||||
break;
|
||||
default:
|
||||
// 对于未知类型,使用完整的层级管理
|
||||
this.ensureCorrectLayerOrder();
|
||||
|
@ -1,9 +1,13 @@
|
||||
import type { MapPen } from '@api/map';
|
||||
import { MapPointType } from '@api/map';
|
||||
import type { StorageLocationInfo, StorageLocationMessage } from '@api/scene';
|
||||
import { monitorStorageLocationById } from '@api/scene';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { type Ref, ref } from 'vue';
|
||||
|
||||
import { createStorageLocationPens } from './draw/storage-location-drawer';
|
||||
|
||||
// 提供给绘制层快速查询的全局状态映射:pointId -> (layerName -> { occupied, locked })
|
||||
export type StorageState = { occupied?: boolean; locked?: boolean };
|
||||
export const storageStateMap = new Map<string, Map<string, StorageState>>();
|
||||
@ -161,12 +165,20 @@ export class StorageLocationService {
|
||||
const inner = new Map<string, StorageState>();
|
||||
list.forEach((loc) => {
|
||||
inner.set(loc.layer_name, { occupied: loc.is_occupied, locked: loc.is_locked });
|
||||
console.log(`更新库位状态映射: ${pointId} - ${loc.layer_name}`, {
|
||||
occupied: loc.is_occupied,
|
||||
locked: loc.is_locked
|
||||
});
|
||||
});
|
||||
storageStateMap.set(pointId, inner);
|
||||
}
|
||||
|
||||
// 更新动作点的边框颜色
|
||||
this.updatePointBorderColors();
|
||||
|
||||
// 重新创建所有动作点的库位pen对象以反映最新状态
|
||||
this.createAll();
|
||||
|
||||
// 批量更新后触发一次重绘
|
||||
this.scheduleRender();
|
||||
} else if (message.type === 'storage_location_status_change') {
|
||||
@ -195,6 +207,12 @@ export class StorageLocationService {
|
||||
locked: new_status.is_locked,
|
||||
});
|
||||
storageStateMap.set(pointId, inner);
|
||||
|
||||
// 重新创建该动作点的库位pen对象以反映最新状态
|
||||
const pointPen = this.editor?.getPenById(pointId);
|
||||
if (pointPen?.point?.associatedStorageLocations) {
|
||||
this.create(pointId, pointPen.point.associatedStorageLocations);
|
||||
}
|
||||
}
|
||||
// 单条状态更新后触发重绘
|
||||
this.scheduleRender();
|
||||
@ -251,6 +269,193 @@ export class StorageLocationService {
|
||||
return this.storageLocations.value.get(pointId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库位(为动作点创建库位pen对象)
|
||||
* @param pointId 动作点ID
|
||||
* @param storageLocations 库位名称列表
|
||||
*/
|
||||
public create(pointId: string, storageLocations: string[]): void {
|
||||
if (!this.editor) return;
|
||||
|
||||
const pointPen = this.editor.getPenById(pointId);
|
||||
if (!pointPen || pointPen.name !== 'point' || pointPen.point?.type !== MapPointType.动作点) {
|
||||
console.warn(`无法为 ${pointId} 创建库位: 不是动作点或点位不存在`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已存在库位pen对象
|
||||
const existingPens = this.getStorageLocationPens(pointId);
|
||||
|
||||
// 如果已存在pen对象,只更新状态,不重新创建
|
||||
if (existingPens.length > 0) {
|
||||
this.update(pointId, storageLocations);
|
||||
return;
|
||||
}
|
||||
|
||||
// 只有在不存在pen对象时才创建新的
|
||||
const pointRect = this.editor.getPointRect(pointPen) ?? { x: 0, y: 0, width: 0, height: 0 };
|
||||
const pens = createStorageLocationPens(pointId, storageLocations, pointRect, storageStateMap);
|
||||
|
||||
// 添加所有pen对象到编辑器
|
||||
pens.forEach(pen => {
|
||||
this.editor!.addPen(pen, false, true, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新库位状态
|
||||
* @param pointId 动作点ID
|
||||
* @param updates 更新操作,可以是单个库位或批量更新
|
||||
* @param state 单个库位状态(当updates为字符串时使用)
|
||||
*/
|
||||
public update(
|
||||
pointId: string,
|
||||
updates: string | Record<string, { occupied?: boolean; locked?: boolean }> | string[],
|
||||
state?: { occupied?: boolean; locked?: boolean }
|
||||
): void {
|
||||
if (!this.editor) return;
|
||||
|
||||
// 处理库位名称列表更新(用于重新创建时更新状态)
|
||||
if (Array.isArray(updates)) {
|
||||
const states: Record<string, { occupied: boolean; locked: boolean }> = {};
|
||||
updates.forEach(locationName => {
|
||||
const inner = storageStateMap.get(pointId);
|
||||
const state = inner?.get(locationName) || { occupied: false, locked: false };
|
||||
states[locationName] = { occupied: !!state.occupied, locked: !!state.locked };
|
||||
});
|
||||
this.update(pointId, states);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理单个库位更新
|
||||
if (typeof updates === 'string' && state) {
|
||||
// 同步到storageStateMap
|
||||
this.syncStorageStateToMap(pointId, updates, {
|
||||
occupied: state.occupied || false,
|
||||
locked: state.locked || false
|
||||
});
|
||||
|
||||
// 查找并更新pen对象
|
||||
const storagePen = this.findStorageLocationPen(pointId, updates);
|
||||
if (storagePen?.storageLocation) {
|
||||
const updatedState = { ...storagePen.storageLocation, ...state };
|
||||
this.editor.updatePen(storagePen.id!, {
|
||||
storageLocation: updatedState,
|
||||
background: updatedState.occupied ? '#ff4d4f' : '#f5f5f5',
|
||||
}, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理批量更新
|
||||
if (typeof updates === 'object') {
|
||||
// 批量同步状态到storageStateMap
|
||||
Object.entries(updates).forEach(([locationName, state]) => {
|
||||
this.syncStorageStateToMap(pointId, locationName, {
|
||||
occupied: state.occupied || false,
|
||||
locked: state.locked || false
|
||||
});
|
||||
});
|
||||
|
||||
// 批量更新pen对象
|
||||
const storagePens = this.getStorageLocationPens(pointId);
|
||||
storagePens.forEach(pen => {
|
||||
if (pen.storageLocation) {
|
||||
const locationName = pen.storageLocation.locationName;
|
||||
const state = updates[locationName];
|
||||
|
||||
if (state && this.editor) {
|
||||
const updatedState = { ...pen.storageLocation, ...state };
|
||||
this.editor.updatePen(pen.id!, {
|
||||
storageLocation: updatedState,
|
||||
background: updatedState.occupied ? '#ff4d4f' : '#f5f5f5',
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除库位(删除动作点的所有库位pen对象)
|
||||
* @param pointId 动作点ID
|
||||
*/
|
||||
public delete(pointId: string): void {
|
||||
if (!this.editor) return;
|
||||
|
||||
const storagePens = this.editor.find('storage-location,storage-more,storage-background').filter(
|
||||
(pen) => pen.tags?.includes(`point-${pointId}`)
|
||||
);
|
||||
if (storagePens.length > 0) {
|
||||
this.editor.delete(storagePens, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步库位状态到storageStateMap
|
||||
* @param pointId 动作点ID
|
||||
* @param locationName 库位名称
|
||||
* @param state 库位状态
|
||||
*/
|
||||
private syncStorageStateToMap(
|
||||
pointId: string,
|
||||
locationName: string,
|
||||
state: { occupied: boolean; locked: boolean }
|
||||
): void {
|
||||
if (!storageStateMap.has(pointId)) {
|
||||
storageStateMap.set(pointId, new Map());
|
||||
}
|
||||
const inner = storageStateMap.get(pointId)!;
|
||||
inner.set(locationName, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动作点的库位pen对象列表
|
||||
* @param pointId 动作点ID
|
||||
* @returns 库位pen对象列表
|
||||
*/
|
||||
private getStorageLocationPens(pointId: string): MapPen[] {
|
||||
if (!this.editor) return [];
|
||||
|
||||
return this.editor.find('storage-location,storage-more').filter(
|
||||
(pen) => pen.tags?.includes(`point-${pointId}`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指定的库位pen对象
|
||||
* @param pointId 动作点ID
|
||||
* @param locationName 库位名称
|
||||
* @returns 库位pen对象
|
||||
*/
|
||||
private findStorageLocationPen(pointId: string, locationName: string): MapPen | undefined {
|
||||
if (!this.editor) return undefined;
|
||||
|
||||
return this.editor.find('storage-location').find(
|
||||
(pen) => pen.storageLocation?.pointId === pointId && pen.storageLocation?.locationName === locationName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为所有动作点创建库位pen对象
|
||||
* 用于初始化或刷新所有动作点的库位显示
|
||||
*/
|
||||
public createAll(): void {
|
||||
if (!this.editor) return;
|
||||
|
||||
const actionPoints = this.editor.find('point').filter(
|
||||
(pen) => pen.point?.type === MapPointType.动作点 && pen.point?.associatedStorageLocations?.length
|
||||
);
|
||||
|
||||
actionPoints.forEach((pen) => {
|
||||
if (pen.id && pen.point?.associatedStorageLocations) {
|
||||
this.create(pen.id, pen.point.associatedStorageLocations);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
@ -260,3 +465,80 @@ export class StorageLocationService {
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 新增:简化的库位更新API ====================
|
||||
|
||||
/**
|
||||
* 库位状态类型定义
|
||||
*/
|
||||
export interface StorageLocationState {
|
||||
occupied: boolean
|
||||
locked: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 库位更新操作类型
|
||||
*/
|
||||
export interface StorageLocationUpdate {
|
||||
pointId: string
|
||||
locationName: string
|
||||
state: Partial<StorageLocationState>
|
||||
}
|
||||
|
||||
/**
|
||||
* 库位管理API(CRUD操作)
|
||||
* 提供创建-更新-删除-修改的完整功能
|
||||
*/
|
||||
export class StorageLocationUpdater {
|
||||
private service: StorageLocationService;
|
||||
|
||||
constructor(service: StorageLocationService) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库位
|
||||
* @param pointId 动作点ID
|
||||
* @param storageLocations 库位名称列表
|
||||
* @returns 当前实例,支持链式调用
|
||||
*/
|
||||
create(pointId: string, storageLocations: string[]): this {
|
||||
this.service.create(pointId, storageLocations);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新库位状态
|
||||
* @param pointId 动作点ID
|
||||
* @param updates 更新操作,可以是单个库位或批量更新
|
||||
* @param state 单个库位状态(当updates为字符串时使用)
|
||||
* @returns 当前实例,支持链式调用
|
||||
*/
|
||||
update(
|
||||
pointId: string,
|
||||
updates: string | Record<string, Partial<StorageLocationState>>,
|
||||
state?: Partial<StorageLocationState>
|
||||
): this {
|
||||
this.service.update(pointId, updates, state);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除库位
|
||||
* @param pointId 动作点ID
|
||||
* @returns 当前实例,支持链式调用
|
||||
*/
|
||||
delete(pointId: string): this {
|
||||
this.service.delete(pointId);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建库位更新器实例
|
||||
* @param service 库位服务实例
|
||||
* @returns 库位更新器实例
|
||||
*/
|
||||
export function createStorageLocationUpdater(service: StorageLocationService): StorageLocationUpdater {
|
||||
return new StorageLocationUpdater(service);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user