From 0ffb170ec8871ca933a22488e5794010bba23f28 Mon Sep 17 00:00:00 2001 From: xudan Date: Thu, 4 Sep 2025 11:52:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BA=93=E4=BD=8D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=BA=93=E4=BD=8D=E7=9A=84=E5=88=9B=E5=BB=BA=E3=80=81=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=92=8C=E5=88=A0=E9=99=A4=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BA=93=E4=BD=8D=E6=B8=B2=E6=9F=93=E5=92=8C=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/map/type.ts | 12 + src/services/draw/storage-location-drawer.ts | 273 +++++++++++++++++- src/services/editor.service.ts | 140 +++++++-- src/services/layer-manager.service.ts | 18 ++ src/services/storage-location.service.ts | 282 +++++++++++++++++++ 5 files changed, 708 insertions(+), 17 deletions(-) diff --git a/src/apis/map/type.ts b/src/apis/map/type.ts index 8cd60c0..4010f56 100644 --- a/src/apis/map/type.ts +++ b/src/apis/map/type.ts @@ -11,6 +11,7 @@ export interface MapPen extends Pen { route?: MapRouteInfo; // 线路信息 area?: MapAreaInfo; // 区域信息 robot?: MapRobotInfo; // 实时机器人信息 + storageLocation?: MapStorageLocationInfo; // 库位信息 attrs?: Record; // 额外属性 activeAttrs?: Array; // 已激活的额外属性 @@ -61,6 +62,17 @@ export interface MapAreaInfo { export type MapRobotInfo = Pick; //#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'; diff --git a/src/services/draw/storage-location-drawer.ts b/src/services/draw/storage-location-drawer.ts index 307b28b..a42cb26 100644 --- a/src/services/draw/storage-location-drawer.ts +++ b/src/services/draw/storage-location-drawer.ts @@ -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> +): 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; +} diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index aadaf5b..069effa 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -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, + 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): 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个子元素的拖放功能 (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 辅助函数 diff --git a/src/services/layer-manager.service.ts b/src/services/layer-manager.service.ts index de7ac5a..e15530b 100644 --- a/src/services/layer-manager.service.ts +++ b/src/services/layer-manager.service.ts @@ -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(); diff --git a/src/services/storage-location.service.ts b/src/services/storage-location.service.ts index 53735f9..f2b2bbe 100644 --- a/src/services/storage-location.service.ts +++ b/src/services/storage-location.service.ts @@ -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>(); @@ -161,12 +165,20 @@ export class StorageLocationService { const inner = new Map(); 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[], + state?: { occupied?: boolean; locked?: boolean } + ): void { + if (!this.editor) return; + + // 处理库位名称列表更新(用于重新创建时更新状态) + if (Array.isArray(updates)) { + const states: Record = {}; + 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 +} + +/** + * 库位管理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>, + state?: Partial + ): 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); +}