From a0e33b2e866511db60350158ea83b505b3b3beca Mon Sep 17 00:00:00 2001 From: xudan Date: Mon, 20 Oct 2025 19:02:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(area-detail-card):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=97=A8=E5=8C=BA=E5=9F=9F=E7=BB=91=E5=AE=9A=E8=B7=AF=E6=AE=B5?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E4=BC=98=E5=8C=96=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/card/area-detail-card.vue | 36 ++++-- src/pages/scene-editor.vue | 2 + src/services/editor.service.ts | 138 ++++++++++++++--------- 3 files changed, 113 insertions(+), 63 deletions(-) diff --git a/src/components/card/area-detail-card.vue b/src/components/card/area-detail-card.vue index 4abfdb4..eeae83e 100644 --- a/src/components/card/area-detail-card.vue +++ b/src/components/card/area-detail-card.vue @@ -47,6 +47,15 @@ const bindPoint = computed( .join('、') ?? '', ); +// 门区域:绑定路段(以可读名称拼接) +const bindRoutes = computed( + () => + area.value?.routes + ?.map((rid) => editor.value.getRouteLabel(rid)) + .filter((v) => !!v) + .join('、') ?? '', +); + const ruleText = computed(() => { if (area.value?.inoutflag === 1) return '先进先出'; if (area.value?.inoutflag === 2) return '后进先出'; @@ -74,26 +83,39 @@ const ruleText = computed(() => { - {{ $t('最大可容纳AMR数') }} - {{ pen.area?.maxAmr ?? $t('暂无') }} + {{ $t('最大AMR数') }} + {{ pen.area?.maxAmr ?? $t('无') }} - {{ $t('库区规则') }} - {{ ruleText || $t('暂无') }} + {{ $t('进出规则') }} + {{ ruleText || $t('无') }} {{ $t('绑定库位') }} - {{ bindStorageLocations || $t('暂无') }} + {{ bindStorageLocations || $t('无') }} + + + + + + {{ $t('设备ID') }} + {{ area.doorDeviceId || $t('无') }} + + + + + {{ $t('已绑定路段') }} + {{ bindRoutes || $t('无') }} - {{ $t('绑定站点') }} - {{ bindPoint || $t('暂无') }} + {{ $t('点位') }} + {{ bindPoint || $t('无') }} diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue index 4334600..06e0a79 100644 --- a/src/pages/scene-editor.vue +++ b/src/pages/scene-editor.vue @@ -125,6 +125,8 @@ const syncDoorDeviceToRoutes = () => { }); }; const saveScene = async (payload?: SaveScenePayload) => { + // 保存前,同步门设备到绑定的路段,确保路段也带有 deviceId 等信息 + try { syncDoorDeviceToRoutes(); } catch {} const currentJson = payload?.json ?? editor.value?.save(); if (currentJson) { try { diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index 1b14ae9..2b700e5 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -21,9 +21,9 @@ import type { StandardSceneRoute, } from '@api/scene'; import sTheme from '@core/theme.service'; -import { LockState, Meta2d, type Pen } from '@meta2d/core'; +import { LockState, Meta2d, type Pen } from '@meta2d/core'; import { useObservable } from '@vueuse/rxjs'; -import { clone, get, isEmpty, isNil, isString, pick, } from 'lodash-es'; +import { clone, get, isEmpty, isNil, isString, pick } from 'lodash-es'; import { debounceTime, filter, map, Subject, switchMap } from 'rxjs'; import type { Ref } from 'vue'; import { watch } from 'vue'; @@ -31,18 +31,14 @@ import { watch } from 'vue'; import { AreaOperationService } from './area-operation.service'; import { BinTaskManagerService } from './bintask-manager.service'; import colorConfig from './color/color-config.service'; -import { - drawStorageBackground, - drawStorageLocation, - drawStorageMore, -} from './draw/storage-location-drawer'; +import { drawStorageBackground, drawStorageLocation, drawStorageMore } from './draw/storage-location-drawer'; import { AreaManager } from './editor/area-manager.service'; import { anchorPoint, drawArea, drawLine, drawPoint, lineBezier2, lineBezier3 } from './editor/editor-drawers'; -import { drawRobot,EditorRobotService } from './editor/editor-robot.service'; +import { drawRobot, EditorRobotService } from './editor/editor-robot.service'; import { PointManager } from './editor/point-manager.service'; import { RouteManager } from './editor/route-manager.service'; import { LayerManagerService } from './layer-manager.service'; -import { createStorageLocationUpdater,StorageLocationService } from './storage-location.service'; +import { createStorageLocationUpdater, StorageLocationService } from './storage-location.service'; import { AutoStorageGenerator } from './utils/auto-storage-generator'; /** @@ -90,7 +86,7 @@ export class EditorService extends Meta2d { detail?: Partial, isImport = false, ): Promise { - const sceneData = (isString(map) ? (map ? JSON.parse(map) : {}) : map); + const sceneData = isString(map) ? (map ? JSON.parse(map) : {}) : map; const scene: StandardScene = sceneData || {}; if (!isEmpty(detail?.group)) { scene.robotGroups = [detail.group]; @@ -99,7 +95,7 @@ export class EditorService extends Meta2d { const { robotGroups, robots, points, routes, areas, robotLabels, ...extraFields } = scene; // 保存所有额外字段(包括width、height等) this.#originalSceneData = extraFields; - + // 颜色配置现在使用本地存储,不再从场景数据加载 this.open(); @@ -210,8 +206,7 @@ export class EditorService extends Meta2d { * @param groups 机器人组列表 * @param robots 机器人信息列表 */ - - + /** * 从场景数据加载点位到画布 * @param points 标准场景点位数据数组 @@ -280,7 +275,7 @@ export class EditorService extends Meta2d { #loadSceneRoutes(routes?: StandardSceneRoute[], isImport = false): void { if (!routes?.length) return; routes.map((v) => { - const { id, desc, from, to, type, pass, c1, c2, properties, maxSpeed } = v; + const { id, desc, from, to, type, pass, c1, c2, properties, maxSpeed } = v as any; const p1 = this.getPenById(from); const p2 = this.getPenById(to); if (isNil(p1) || isNil(p2)) return; @@ -315,6 +310,10 @@ export class EditorService extends Meta2d { c1: transformedC1, c2: transformedC2, maxSpeed, + // 门区域扩展字段 + deviceId: (v as any).deviceId, + deviceStatus: (v as any).deviceStatus, + isConnected: (v as any).isConnected, }, }, { render: false, history: false, doEvent: false }, @@ -330,7 +329,8 @@ export class EditorService extends Meta2d { if (!areas?.length) return; await Promise.all( areas.map(async (v) => { - const { id, name, desc, x, y, w, h, type, points, routes, maxAmr, inoutflag, storageLocations, properties } = v; + const { id, name, desc, x, y, w, h, type, points, routes, maxAmr, inoutflag, storageLocations, properties } = + v as any; // 只有在导入场景文件时才进行反向坐标转换 let finalX = x; let finalY = y; @@ -348,7 +348,7 @@ export class EditorService extends Meta2d { await this.addArea({ x: finalX, y: finalY }, { x: finalX + finalW, y: finalY + finalH }, type, id); // 对于库区类型,需要将点位名称数组转换为点位ID数组,并更新动作点的库位信息 - let processedPoints = points; + let processedPoints: string[] | undefined = points as any; if (type === MapAreaType.库区 && points?.length) { // 将点位名称数组转换为点位ID数组 const actionPoints = this.find('point').filter( @@ -363,7 +363,7 @@ export class EditorService extends Meta2d { const storageLocationsMap: Record = {}; storageLocations.forEach((item) => { Object.entries(item).forEach(([pointName, locations]) => { - storageLocationsMap[pointName] = locations; + storageLocationsMap[pointName] = locations as unknown as string[]; }); }); @@ -380,7 +380,23 @@ export class EditorService extends Meta2d { } this.setValue( - { id, label: name, desc, properties, area: { type, points: processedPoints, routes, maxAmr, inoutflag } }, + { + id, + label: name, + desc, + properties, + area: { + type, + points: processedPoints, + routes, + maxAmr, + inoutflag, + // 门区域扩展字段 + doorDeviceId: (v as any).doorDeviceId, + deviceStatus: (v as any).deviceStatus, + isConnected: (v as any).isConnected, + }, + }, { render: false, history: false, doEvent: false }, ); }), @@ -426,13 +442,13 @@ export class EditorService extends Meta2d { if (MapPointType.自动门点 === type) { point.deviceId = deviceId; } - + return point; } #mapSceneRoute(pen?: MapPen): StandardSceneRoute | null { if (!pen?.id || pen.anchors?.length !== 2 || isEmpty(pen?.route)) return null; const { id, anchors, desc, properties } = pen; - const { type, direction = 1, pass, c1, c2, maxSpeed } = pen.route; + const { type, direction = 1, pass, c1, c2, maxSpeed, deviceId, deviceStatus, isConnected } = pen.route as any; const [p1, p2] = anchors.map((v) => this.getPenById(v.connectTo!)); if (isNil(p1) || isNil(p2)) return null; const route: StandardSceneRoute = { @@ -446,6 +462,10 @@ export class EditorService extends Meta2d { config: {}, properties, }; + // 门区域扩展字段:保持到顶层,便于后端识别 + (route as any).deviceId = deviceId; + (route as any).deviceStatus = deviceStatus; + (route as any).isConnected = isConnected; const { x: x1, y: y1 } = this.getPointRect(p1)!; const { x: x2, y: y2 } = this.getPointRect(p2)!; const cp1 = { x: x1 + (c1?.x ?? 0), y: y1 + (c1?.y ?? 0) }; @@ -472,7 +492,7 @@ export class EditorService extends Meta2d { #mapSceneArea(pen: MapPen): StandardSceneArea | null { if (!pen.id || isEmpty(pen.area)) return null; const { id, label, desc, properties } = pen; - const { type, points, maxAmr, inoutflag } = pen.area; + const { type, points, routes, maxAmr, inoutflag, doorDeviceId, deviceStatus, isConnected } = pen.area as any; const { x, y, width, height } = this.getPenRect(pen); // 进行坐标转换:左上角原点 -> 中心点原点,同时应用ratio缩放 const transformedCoords = this.#transformCoordinate(x, y); @@ -488,6 +508,11 @@ export class EditorService extends Meta2d { config: {}, properties, }; + // 门区域扩展字段 + (area as any).routes = routes; + (area as any).doorDeviceId = doorDeviceId; + (area as any).deviceStatus = deviceStatus; + (area as any).isConnected = isConnected; if (type === MapAreaType.约束区) { area.maxAmr = maxAmr; } @@ -655,7 +680,6 @@ export class EditorService extends Meta2d { this.robotService.removeRobotsFromAllLabels(robotIds); } - public async initRobots(): Promise { await this.robotService.initRobots(); } @@ -668,7 +692,11 @@ export class EditorService extends Meta2d { this.robotService.updateRobotImage(robotName); } - public updateRobotStatusOverlay(id: string, render = false, newPosition?: { x: number; y: number; rotate: number }): void { + public updateRobotStatusOverlay( + id: string, + render = false, + newPosition?: { x: number; y: number; rotate: number }, + ): void { this.robotService.updateRobotStatusOverlay(id, render, newPosition); } //#endregion @@ -750,7 +778,6 @@ export class EditorService extends Meta2d { * 优化的像素对齐算法 - 确保在所有缩放比例下都能精确对齐像素边界 * 解决小车和光圈在特定缩放比例下不重合的问题 */ - /** 画布变化事件流,用于触发响应式数据更新 */ readonly #change$$ = new Subject(); @@ -874,7 +901,7 @@ export class EditorService extends Meta2d { public updateStorageLocation( pointId: string, updates: string | Record, - state?: { occupied?: boolean; locked?: boolean } + state?: { occupied?: boolean; locked?: boolean }, ): void { this.storageLocationService?.update(pointId, updates, state); } @@ -905,12 +932,15 @@ export class EditorService extends Meta2d { */ public createAllStorageLocationPens(): void { if (!this.storageLocationService) return; - + // 使用 requestIdleCallback 在浏览器空闲时执行,避免阻塞UI if (typeof requestIdleCallback !== 'undefined') { - requestIdleCallback(() => { - this.storageLocationService?.createAll(); - }, { timeout: 200 }); + requestIdleCallback( + () => { + this.storageLocationService?.createAll(); + }, + { timeout: 200 }, + ); } else { // 降级到 requestAnimationFrame requestAnimationFrame(() => { @@ -944,7 +974,10 @@ export class EditorService extends Meta2d { * @param areaName 库区名称 * @returns 生成结果统计 */ - public batchGenerateStorageForPoints(pointIds: string[], areaName: string): { + public batchGenerateStorageForPoints( + pointIds: string[], + areaName: string, + ): { success: number; skipped: number; failed: number; @@ -953,8 +986,6 @@ export class EditorService extends Meta2d { return this.autoStorageGenerator.batchGenerateStorageForPoints(pointIds, areaName); } - - /** * 根据设备ID更新自动门点状态 * @param deviceId 设备ID @@ -1080,8 +1111,6 @@ export class EditorService extends Meta2d { this.pointManager.changePointType(id, type); } - - //#endregion //#region 线路 @@ -1196,7 +1225,7 @@ export class EditorService extends Meta2d { (container.children.item(5)).ondrop = null; // 监听所有画布事件 this.on('*', (e, v) => this.#listen(e, v)); - + // 添加额外的右键事件监听器,确保阻止默认行为 const canvasElement = this.canvas as unknown as HTMLCanvasElement; if (canvasElement && canvasElement.addEventListener) { @@ -1208,7 +1237,7 @@ export class EditorService extends Meta2d { true, ); } - + // 注册自定义绘制函数和锚点 this.#register(); @@ -1268,7 +1297,6 @@ export class EditorService extends Meta2d { }); } - #listen(e: unknown, v: any) { switch (e) { case 'opened': @@ -1372,14 +1400,14 @@ export class EditorService extends Meta2d { * @param pointType 新的点位类型 */ public batchUpdatePointType(pointIds: string[], pointType: MapPointType): void { - pointIds.forEach(id => { + pointIds.forEach((id) => { const pen = this.getPenById(id); if (pen?.name === 'point') { this.updatePen(id, { point: { ...pen.point, - type: pointType - } + type: pointType, + }, }); } }); @@ -1391,14 +1419,14 @@ export class EditorService extends Meta2d { * @param routeType 新的路线类型 */ public batchUpdateRouteType(routeIds: string[], routeType: MapRouteType): void { - routeIds.forEach(id => { + routeIds.forEach((id) => { const pen = this.getPenById(id); if (pen?.name === 'line' && pen.route) { this.updatePen(id, { route: { ...pen.route, - type: routeType - } + type: routeType, + }, }); } }); @@ -1410,14 +1438,14 @@ export class EditorService extends Meta2d { * @param passType 新的通行类型 */ public batchUpdateRoutePassType(routeIds: string[], passType: MapRoutePassType): void { - routeIds.forEach(id => { + routeIds.forEach((id) => { const pen = this.getPenById(id); if (pen?.name === 'line' && pen.route) { this.updatePen(id, { route: { ...pen.route, - pass: passType - } + pass: passType, + }, }); } }); @@ -1429,14 +1457,14 @@ export class EditorService extends Meta2d { * @param direction 新的方向 */ public batchUpdateRouteDirection(routeIds: string[], direction: 1 | -1): void { - routeIds.forEach(id => { + routeIds.forEach((id) => { const pen = this.getPenById(id); if (pen?.name === 'line' && pen.route) { this.updatePen(id, { route: { ...pen.route, - direction - } + direction, + }, }); } }); @@ -1457,21 +1485,19 @@ export class EditorService extends Meta2d { #register() { this.register({ line: () => new Path2D() }); - this.registerCanvasDraw({ - point: drawPoint, - line: drawLine, - area: drawArea, + this.registerCanvasDraw({ + point: drawPoint, + line: drawLine, + area: drawArea, robot: drawRobot, 'storage-location': drawStorageLocation, 'storage-more': drawStorageMore, - 'storage-background': drawStorageBackground + 'storage-background': drawStorageBackground, }); this.registerAnchors({ point: anchorPoint }); this.addDrawLineFn('bezier2', lineBezier2); this.addDrawLineFn('bezier3', lineBezier3); } - - } //#region 自定义绘制函数