feat(area-detail-card): 新增门区域绑定路段显示,优化区域信息展示逻辑

This commit is contained in:
xudan 2025-10-20 19:02:48 +08:00
parent 2978c58b42
commit a0e33b2e86
3 changed files with 113 additions and 63 deletions

View File

@ -47,6 +47,15 @@ const bindPoint = computed<string>(
.join('、') ?? '',
);
//
const bindRoutes = computed<string>(
() =>
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(() => {
<a-list class="block mt-16">
<a-list-item v-if="area.type === MapAreaType.约束区">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('最大可容纳AMR数') }}</a-typography-text>
<a-typography-text>{{ pen.area?.maxAmr ?? $t('无') }}</a-typography-text>
<a-typography-text type="secondary">{{ $t('最大AMR数') }}</a-typography-text>
<a-typography-text>{{ pen.area?.maxAmr ?? $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="MapAreaType.库区 === area.type">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('库区规则') }}</a-typography-text>
<a-typography-text>{{ ruleText || $t('无') }}</a-typography-text>
<a-typography-text type="secondary">{{ $t('进出规则') }}</a-typography-text>
<a-typography-text>{{ ruleText || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="MapAreaType.库区 === area.type">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('绑定库位') }}</a-typography-text>
<a-typography-text>{{ bindStorageLocations || $t('暂无') }}</a-typography-text>
<a-typography-text>{{ bindStorageLocations || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<!-- 门区域显示设备与绑定路段 -->
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('设备ID') }}</a-typography-text>
<a-typography-text>{{ area.doorDeviceId || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="area && (area.type as any) === DOOR_AREA_TYPE">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('已绑定路段') }}</a-typography-text>
<a-typography-text>{{ bindRoutes || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
<a-list-item v-if="[MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(area.type)">
<a-flex :gap="8" vertical>
<a-typography-text type="secondary">{{ $t('绑定站点') }}</a-typography-text>
<a-typography-text>{{ bindPoint || $t('暂无') }}</a-typography-text>
<a-typography-text type="secondary">{{ $t('') }}</a-typography-text>
<a-typography-text>{{ bindPoint || $t('无') }}</a-typography-text>
</a-flex>
</a-list-item>
</a-list>

View File

@ -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 {

View File

@ -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<GroupSceneDetail>,
isImport = false,
): Promise<void> {
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<string, string[]> = {};
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<void> {
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<boolean>();
@ -874,7 +901,7 @@ export class EditorService extends Meta2d {
public updateStorageLocation(
pointId: string,
updates: string | Record<string, { occupied?: boolean; locked?: boolean }>,
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 {
(<HTMLDivElement>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 自定义绘制函数